Java类的生命周期

来源:互联网 发布:电子秤数据倒显 编辑:程序博客网 时间:2024/05/16 18:16
// 类的生命周期从类被加载,链接,初始化开始,到类被卸载,类的二进制数据位于方法区内,在堆区内还会有一个相应的描述这个类的Class对象
// 当java命令运行一个java程序时,就启动一个java虚拟机进程,java虚拟机从启动到终止的过程,称为java虚拟机的生命周期,
// 以下情况会使java虚拟机结束生命周期:程序正常结束,程序出现异常,执行了system.exit()方法,操作出错导致java虚拟机进程终止
// 类的加载,连接和初始化,加载:查找并加载类的二进制数据,连接,包括验证(确保被加载类的正确性),准备(为类的静态变量分配内存,将其初始化为默认值),解析(把类中的符号引用转换为直接引用)类的二进制数据
// 初始化:给类的静态变量赋予正确的初始值
// 类的加载:将类的.class文件中的二进制数据读入到内存中,把他存放在运行时数据区的方法区内,然后在堆区创建一个Class对象,用来封装类在方法区内的数据结构
// 类的加载的最终产品是位于运行时数据区的堆区的Class对象,Class对象封装了类在方法区内的数据结构,并且向java程序提供了访问类在方法区内的数据结构的接口
// 类的加载是由类加载器完成的,类加载可分为两种:1.java虚拟机自带的加载器,包括启动类加载器,扩展类加载器,系统类加载器 2.用户自定义的类加载器,是ClassLoader类的子类,可以通过它来定制类的加载方式
// 使用类加载器并不需要等到某个类被"首次主动使用"时才加载它,java虚拟机允许类加载器在预料某个类将要被使用时就预先加载它。
// 类的验证,确保类文件遵从java类文件的固定格式,语义检查比如验证final类型的类有没有子类,字节码验证,二进制兼容的验证
// 类的准备,java虚拟机为类的静态变量分配内存,并设置默认值
// 类的解析,java虚拟机会把符号引用替换为一个指针,该指针指向该方法在方法区中的内存位置,这个指针就是直接引用。
// 类的初始化,静态变量赋予初始值。java虚拟机会按照初始化语句在类文件中的先后顺序来依次执行他们,假如这个类还没有被加载和链接,那就先进行加载和链接,假如这个类存在直接的父类,那就先初始化直接父类,假如类中存在初始化语句,那就依次执行这些初始化语句
// 类的初始化时机,只有六种活动被看做是程序对类或接口的主动使用,1创建类的实例,new,反射,克隆,反序列化手段(把一个User实例写入文件,再从文件中读出,保存对象状态的机制)来创建实例,2.调用类的静态方法,3访问某个类和接口的静态变量,或者对该静态变量赋值
//             4.调用Java中的某些反射方法,Class.forName("Worker"); 5.初始化一个类的子类会先初始化他的父类,6java虚拟机启动时被标明为启动类的类
// 除了以上,其他使用java类的方式都被看做是被动使用,不会导致类初始化,1.对于final类型的静态变量,2.当java虚拟机初始化一个类时,要求他的所有父类都已经被初始化,但是这条规则并不适用于接口,一个父接口不会因为他的子类的初始化而初始化
// 3.调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化(ClassLoader d = ClassLoader.getSystemClassLoader(); d.loadClass("Test.Test6");),
// 只有调用Class类的静态方法 forName(Test.Test6)方式显示初始化Test6时,才是对ClassA的主动使用,将导致ClassA被初始化,他的静态代码块也会被执行
// 类的加载用来把类加载到java虚拟机中,类的加载采用父亲委托机制,除了java自带的根类加载器以外,其余的类加载器都有且只有一个父加载器,例如当java程序请求加载器loader1加载simple类时,loader1首先委托自己的父加载器去加载Sample类
// 若父加载器能加载,则由父加载器完成加载任务,否则才由加载器loader1本身加载simple类。
// java虚拟机自带以下几种加载器:
// 根Bootstrap类加载器,该加载器没有父加载器,他复制加载虚拟机的核心类库,如java.lang.*,根加载器的实现依赖于底层操作系统,属于虚拟机实现的一部分,他没有继承java.lang.ClassLoader类
// 扩展Extension类加载器,他的父加载器为根加载器,他从java.ext.dirs系统属性所指定的目录中加载类库,或者从jdk安装目录的jre/lib/ext子目录下加载类库,用户把创建的jar文件放在这个目录下
// 也会自动由扩展类加载器加载,扩展类加载器是纯java类,是ClassLoader类的子类
// 系统System类加载器,也称为应用类加载器,他的父加载器为扩展类加载器,他从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,他是用户自定义的类加载器的默认父加载器,系统类加载器是纯java类,是java.lang.ClassLoader类的子类
// 所有用户自定义的类加载器应该继承ClassLoader类,父加载器都是系统加载器,
// 现在假如java程序要求loader2加载simple类,Class sampleClass = loader2.loadClass("Sample");loader2首先查看自己的命名空间中查找Sample类是否已经被加载,如果已经加载,则直接返回Sample的Class对象的引用
// 如果Sample类还没有被加载,则依次请求loader1,系统类加载器,扩展类加载器,根类加载器代为加载,次序是从根到loader1顺序,如果都不能加载,则由loader2自己加载,如果自己也不能加载则抛出ClassNotFoundException异常
// 如果有一个类加载器能加载这个类,则这个加载器被称为定义类加载器,能够成功返回Class对象的引用的类加载器(包括定义类加载器)都被称为初始类加载器,例如loader1加载了Sample类,则loader1为Sample类的定义类加载器,loader2和loader1为Simple类的初始类加载器
// 父子类加载之间的不是继承关系,而是包装关系,例如:ClassLoader loader1 = new MyClassLoader(); 参数loader1将作为loader2的父加载器,ClassLoader loader2 = new MyClassLoader(loader1);将loader1父加载器包装进来
// 这样由上自下的加载可以让下层的类加载器不可能加载应该由父加载器加载的可靠类,java.lang.Object类总是由根类加载器加载.
// 命名空间,每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成,在同一个命名空间中,不会出现类的完整名字(类的包名)相同的两个类,在不同的命名空间内可能出现类的完整名字(包括类的包名)相同的两个类
// 运行时包,由同一类加载器加载的属于相同包的类组成的类组成了运行时包,决定两个类是不是属于同一个运行包时,不仅要看包名是否相同,还要看定义类加载器是否相同,只有属于同一运行包时包的类才能访问包可见(默认访问级别)的类和类成员,这样能避免用户自定义的类冒充核心类库的类
// 去访问核心类库的包可见成员,例如用户自定义一个类java.lang.Spy,并由自定义类加载器加载,由于java.lang.Spy和核心类库java.lang.*由不同的加载器加载,他们属于不同的运行时包,所以java.lang.Spy不能访问核心类库java.lang包中的包可见成员(默认访问级别)
// 用户自定义类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖他的findClass方法即可,该方法根据参数指定的类的名字,返回对应的Class对象的引用
// 以下是自定义类加载器MyClassLoader,从path属性指定的文件目录加载.class文件,私有方法loadClassData(String name)能根据参数指定的类的名字,把相应的.class文件中的二进制数据加载到内存中,并且以字节数组的形式返回
// jdk中提供了一个功能比较强大的URLClassLoader类,他扩展了ClassLoader类
// URLClassLoader 不仅能从本地文件系统加载类,还可以从网上下载类,java程序可以直接用URLClassLoader类作为用户自定义的类加载器,URLClassLoader提供了以下构造方法:
// URLClassLoader(URL[] urls) 父加载器为系统类加载器,URLClassLoader(URL[] urls, ClassLoader parent) parent参数指定父加载器
// 类的卸载:当Simple类的Class对象不再被引用,Class对象就会结束生命周期,java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载,java虚拟机自带的类加载器包括根类加载器,扩展类加载器,系统类加载器,java虚拟机本身会始终引用这些类加载器,而
// 这些类加载器始终会引用他们所加载的类的Class对象,因此他们的Class对象始终是可触及的,而用户自定义的类加载器所加载的类是可以被卸载的,以MyClassLoader为例
// Simple.class和new Simple().getClass(),Class.forName("Simple") 获取的Class对象都是指向同一个Simple类的二进制数据,将自定义的类加载器加载的类设置为null,则此Sample对象结束生命周期,MyClassLoader对象结束生命周期,代表Simple类的Class对象也结束生命周期,Simple类在方法区的二进制数据也被卸载了

// 接着重新再加载时,生成的hashcode值和之前的hashcode值是不一样的,代表第二次生成一个新的代表Simple类的Class实例,Simple类先后加载了二次。

class MyClassLoader extends ClassLoader{// 给类加载器指定一个名字,本例中用于区分不同的加载器对象private String name;private String path = "d:\\";private final String fileType = ".class";public MyClassLoader(String name) {super();this.name = name;}public MyClassLoader(ClassLoader parent, String name){super();this.name = name;}public String toString(){return name;}public void setPath(String path){this.path = path;}public String getPath(){return path;}protected Class findClass(String name) throws ClassNotFoundException{byte[] data = loadClassData(name);return defineClass(name, data, 0, data.length);}private byte[] loadClassData(String name) throws ClassNotFoundException{FileInputStream fis = null;byte[] data = null;ByteArrayOutputStream baos = null;try {name = name.replaceAll("\\.", "\\\\");// 把.转换成\fis = new FileInputStream(new File(path + name + fileType));baos = new ByteArrayOutputStream();int ch = 0;while ((ch = fis.read())!=-1) {baos.write(ch);}data = baos.toByteArray();} catch (IOException e) {throw new ClassNotFoundException("class is not find" + name,e);} finally{try {fis.close();baos.close();} catch (Exception e2) {e2.printStackTrace();}}return data;}}public class Test6 {public static void main(String[] args) throws Exception {//ClassLoader d = ClassLoader.getSystemClassLoader();// 获得系统类加载器//System.out.println(d.getParent());//系统类加载器的父类是扩展类加载器//Class objClass= d.loadClass("Test.Test6");//此时不会导致类的初始化//Class c = objClass.forName("Test.Test6");//此时才会导致类的初始化,前面只是加载类//System.out.println(c.getClassLoader());//用户Test6是由系统类加载器加载的,而java.lang.Object是由根类加载器加载的//T t = new T();//t.t1();// 测试自定义类加载器MyClassLoader loader1 = new MyClassLoader("loader1");loader1.setPath("D:\\myapp\\serverlib\\");// 指定当前类加载器加载指定目录下的类MyClassLoader loader2 = new MyClassLoader(loader1 ,"loader2");//指定loader1为父加载器loader2.setPath("D:\\myapp\\clientlib\\");// 指定当前类加载器加载指定目录下的类MyClassLoader loader3 = new MyClassLoader(null ,"loader3");//指定根加载器为父加载器loader3.setPath("D:\\myapp\\otherlib\\");// 指定当前类加载器加载指定目录下的类test1(loader2);//测试类加载器的用法test1(loader3);//测试类加载器的用法// 目前有loader1,loader2,loader3三个类加载器,接着将MyClassLoader的class文件拷贝到D:\myapp\syslib目录下,以他为classpath,使用MyClassLoader类由系统类加载器加载// 从以上的测试看,将Sample和Dog类的class文件同时放到serverlib和clientlib目录下,loader1和loader3各自的命名空间中都有Sample类和Dog类,// 而当serverlib和syslib目录下都存在Sample和Dog类的class文件,那么当使用loader1加载时还是会由系统类加载器加载这两个类,都不存在则抛出ClassNotFoundException异常// 不同类加载器的命名空间存在以下关系:// 同一个命名空间内的类是相互可见的,子类可以看见父类加载器加载的类,例如系统类加载器加载的类能看见根类加载器加载的类,而由父加载器加载的类不能看见子加载器加载的类// 如果两个类加载器之间没有父子关系,那么各自加载的类相互不可见// 测试将Sample.class仅仅拷贝到serverlib目录下,而MyClassLoader的class文件还是放在syslib目录下,因此MyClassLoader类看不见Sample类,所以只能用反射来访问对方的实例的属性和方法// class objClass = loader1.loadClass("Sample");  Field d = objClass.getField("v1"); int v1 = d.getInt(obj);}public static void test1(ClassLoader loader) throws Exception{Class objClass = loader.loadClass("Test.Test6");Object obj = objClass.newInstance();// 创建一个Sample类的实例objClass.forName("Test.Test6").newInstance();// 也是创建一个实例}public void test(){System.out.println("goto");}}class T{public void t1() throws InstantiationException, IllegalAccessException, ClassNotFoundException{Test6 test6 = (Test6)Class.forName("Test.Test6").newInstance();test6.test();}}


0 0
原创粉丝点击