转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6536048.html
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终成为被虚拟机直接使用的Java对象,这就是JVM的类加载机制。
Java天生的可动态扩展的语言特性就是依赖运行期的动态加载和动态连接实现的。
一:类的生命周期
类的生命周期包括7个部分:加载——验证——准备——解析——初始化——使用——卸载
其中,验证——准备——解析 称为连接阶段。除了解析外,其他阶段是顺序发生的,而解析可以与这些阶段交叉进行,因为Java支持动态绑定(晚期绑定),需要运行时才能确定具体类型。
二:类的初始化触发
类的加载机制没有明确的触发条件,但是有5种情况下必须对类进行初始化,那么 加载——验证——准备 就必须在此之前完成了。
1:new、getstatic、putstatic、invokestatic这4个 字节码指令 时对类进行初始化(即:实例化对象、读写静态对象、调用静态方法时,进行类的初始化);
2:使用反射机制对类进行调用时,进行类的初始化;
3:初始化一个类,其父类没有初始化时,先初始化其父类;
4:虚拟机启动时,初始化一个执行主类;
5:使用JDK1.7的动态语言支持时,如果MethodHandle实例的解析结果为REF_getstatic、REF_putstatic、REF_invokestatic的方法句柄(即:读写静态对象或者调用静态方法),则初始化该句柄对应类;
一般,以上5种情况最常见的是前三种:实例化对象、读写静态对象、调用静态方法、反射机制调用类、调用子类触发父类初始化。
三:类的加载过程
从用户角度来说,类(对象)的生命周期只需笼统理解为“加载——使用——卸载”即可,无需太过深入。所以,这里的类加载过程就是我们说的 加载——验证——准备——解析——初始化 这五个使用前的阶段。
1:加载
加载阶段,虚拟机需要完成三件事:通过类名字获取类的二进制字节流——将字节流的内容转存到方法区——在内存中生成一个Class对象作为该类方法区数据的访问入口。
其中,第一步:通过类名获取类的二进制字节流是通过类加载器来完成的。其加载过程使用“双亲委派模型”:
类加载器的层次结构为:
启动类加载器:加载系统环境变量下JAVA_HOME/lib目录下的类库。
扩展类加载器:加载JAVA_HOME/lib/ext目录下的类库。
应用程序类加载器(系统类加载器):加载用户类路径Class_Path指定的类库。(我们可以在使用第三方插件时,把jar包添加到ClassPath后就是使用了这个加载器)
自定义加载器:如果需要自定义加载时的规则(比如:指定类的字节流来源、动态加载时性能优化等),可以自己实现类加载器。
双亲委派模型是指:当一个类加载器收到类加载请求时,不会直接加载这个类,而是把这个加载请求委派给自己父加载器去完成。如果父加载器无法加载时,子加载器才会去尝试加载。
采用双亲委派模型的原因:避免同一个类被多个类加载器重复加载。
2:验证
确保class文件的二进制字节流中包含的信息符号虚拟机要求,包括:文件格式验证、元数据验证(数据语义分析)、字节码验证(数据流语义合法性)、符号引用验证(符号引用的匹配性校验,确保解析能正确执行)
3:准备
为类变量(静态变量)在方法区分配内存,并设置零值。注意:这里是类变量,不是实例变量,实例变量是对象分配到堆内存时根据运行时动态生成的。
4:解析
把常量池中的符号引用解析为直接引用:根据符号引用所作的描述,在内存中找到符合描述的目标并把目标指针指针返回。
5:初始化
真正开始执行Java程序代码,该步执行<clinit>方法根据代码赋值语句,对 类变量和其他资源 进行初始化赋值。
<clinit>方法:编译器自动收集类中所有 类变量的赋值语句和静态语句合并而成,收集的顺序是在程序代码出现的顺序。所以,静态语句中只能访问到定义在静态语句块之前的变量,在其之后的变量可以赋值(相当于新建并赋值了)但不可以访问(因为还没出现)。
注:由此步我们就可以得知,我们在分析向上转型的例子时的程序代码的运行顺序了:父类静态内容——子类静态内容——父类构造——子类构造——子类方法 。
在经历了上面5步“加载”阶段后,才真正地可以使用class对象或者使用实例对象。使用过后,不再需要用到该类的class对象或者实例对象时,就会把类卸载掉(发生在方法区的垃圾回收:无用类的卸载)。
四:对象的生命周期
对象是由类创建出来的,所以对象的生命周期就是包含在类的生命周期中:
类加载(5步)——创建类的实例对象——使用对象——对象回收——类卸载