类加载机制
类从被加载到虚拟机内存开始,到卸载出内存为止,它的生命周期包括7个阶段:加载、验证、准备、解析、初始化、使用和卸载。
一、加载
在加载阶段,虚拟机完成3件事:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
Java虚拟机没有指明二进制字节流要从一个Class文件中获取,也可以从别的地方获取: 从ZIP包中读取 从网络中获取 运行时计算生成(Java动态代理技术) 由其他文件生成(由JSP文件生成对应的Class类) 从数据库中读取 ......
- 加二进制字节流存储在方法区中(按照虚拟机所需的格式存储)。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
Class对象比较特殊,存放在方法中(HotSpot虚拟机)
二、验证
验证的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
一般包括两个方面:
- 格式语义校验:
例如: 是否以0xCAFEBASE开头 主、次版本号是否在当前虚拟机处理范围内 ......
- 代码逻辑校验
三、准备
准备阶段正式为静态变量分配内存并设置初始值,这些静态变量在方法区中分配内存。
注意:
- 准备阶段,JVM只会为静态变量(static修饰)分配内存,不包括实例变量,实例变量将会在对象实例化时随对象一起分配在Java堆中。
准备阶段,只会为value分配内存,不会为name分配内存 public static int value = 123; private String name = "Tom";
- 设置初始值:
-
通常情况(无final):零值
准备阶段,未执行任何Java方法,而value赋值为123指令是程序编译后,存放于类构造器方法中,在初始化阶段才会执行,因此准备阶段,会设置零值。
准备阶段,会设置零值 public static int value = 123;
-
特殊情况(有final):实际值
常量,准备阶段会设置实际值123 public static final int value = 123;
四、解析
解析阶段是虚拟机将常量池内的符号引用替换为直接在其内存中的直接引用的过程。
JVM主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行解析。
五、初始化
到了初始化阶段,才真正开始执行类中定义的Java程序代码。
JVM规定有且仅有5种情况必须对类进行初始化:
-
遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。
对应场景为:
- 使用new关键字实例化对象
- 读取或设置一个类的静态字段(final修饰的除外)
- 调用一个类的静态方法
-
使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
-
当初始化一个类时,需要先初始化父类。
-
当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
-
当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
六、使用
当JVM完成初始化阶段之后,JVM便开始从入口方法开始执行用户的程序代码
七、卸载
当用户程序代码执行完毕后,JVM便开始销毁创建的Class对象,最后负责运行的JVM也退出内存。
类的初始化顺序
实际上Java代码编译成字节码后,没有构造方法的概念,只有类初始化方法和对象初始化方法。
1. 类初始化方法:
编译器会按照其出现顺序,收集静态变量的赋值语句、静态语句块、最终组成类初始化方法。
类初始化方法一般在类初始化的时候执行。
注意:
- 静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但不能访问。
静态语句块可以为i赋值,但不能访问。 public class Test { static { i = 0; System.out.print(i); } static int i = 1; }
- 对于静态变量,只有直接定义这个静态变量的类才会被初始化(执行静态代码块),因此通过子类来引用其父类中定义的静态变量,只会触发父类的初始化而不会触发子类的初始化。
2. 对象初始化方法:
编译器会按照其出现顺序,收集实例变量的赋值语句、普通代码块、最后收集构造函数的代码,最终组成对象初始化方法。
对象初始化方法一般在实例化对象的时候执行。
总结:
一个类的初始化顺序:
1. 初始化静态变量初始值
类加载的准备阶段,JVM会为静态变量初始化零值,为常量(final static修饰)初始化实际值。
2. 初始化入口方法
类加载的初始化阶段,JVM会寻找整个main方法入口,初始化main方法所在的整个类。
3. 执行类初始化方法
JVM会按照其出现的顺序收集静态变量的赋值语句、静态代码块、最终组成类初始化方法,由JVM执行。
4. 执行对象初始化方法
JVM会按照收集实例变量赋值语句、普通代码块、最后收集构造方法,将它们组成对象初始化方法,由JVM执行。
如果在初始化main方法所在类的时候遇到了其他类的初始化,则先加载对应的类,加载完成后返回,最终返回main方法所在类。