7.2 类加载的时机

一个类型从被加载到虚拟机内存中开始,到卸载内存为止,它的整个生命周期将会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段。

在初始化阶段,《Java虚拟机规范》则规定了有且只有六种情况必须立即对类进行“初始化”。

  1. 遇到new getstatic putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段,能够生长这四条指令的典型Java代码场景有:

    1. 使用new关键字实例化对象的时候
    2. 读取或配置一个类型的惊天字段(被final修饰,已在编译器就把结果放入常量池的静态字段除外)的时候。
    3. 调用类型的静态方法的时候
  2. 使用java.lang.reflect包的方法对类型进行反射调动的时候,如果类型没有进行过初始化,则需要先触发其初始化。

  3. 当初始化类的时候,如果发现其父类还没有进行过初始化,则先触发其父类的初始化
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类。
  5. 当使用JDK7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic,REF_putStatic,REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化
  6. 当一个接口中定义了JDK8新加的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在之前被初始化,

被动引用的例子

  1. /**
  2. * 被动使用类字段演示一:
  3. * 通过子类引用父类的静态字段,不会导致子类初始化
  4. */
  5. public class SuperClass {
  6. static {
  7. System.out.println("SuperClass.static 初始值设定项");
  8. }
  9. public static int value=123;
  10. }
  11. public class SubClass extends SuperClass{
  12. static {
  13. System.out.println("SubClass.static 初始值设定项");
  14. }
  15. }
  16. public class NotInitialization {
  17. public static void main(String[] args) {
  18. System.out.println(SubClass.value);
  19. }
  20. }

被动引用例子

通过数组定义来引用类,不会触发此类的初始化。

  1. public static void main(String[] args) {
  2. SuperClass[] sca=new SuperClass[10];
  3. }

被动引用例子三

  1. public class ConstClass {
  2. static {
  3. System.out.println("ConstClass.static 初始值设定项");
  4. }
  5. public static final String HELLOWORLD = "hello world";
  6. }
  7. public class NotInitialization {
  8. public static void main(String[] args) {
  9. //非主动使用那个类字段演示
  10. System.out.println(ConstClass.HELLOWORLD);
  11. }
  12. }

7.3 类加载的过程

7.3.1 加载

在加载阶段,Java虚拟机需要完成以下三件事情。

  1. 通过一个类的全限定名来获取次类的二进制文件流
  2. 将这个字节码所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

相对于类加载过程的其他阶段,非数组类型的加载阶段(准确地说,是加载阶段中获取类的二进制字节流的动作)是开发人员可控性最强的阶段。加载阶段既可以使用Java虚拟机里内置的引导类加载器来完成,也可以由用户自定义的类加载器去完成,开发人员通过定义自己的类加载器去控制字节流的获取方式。实现根据自己想法来赋予应用程序获取运行代码的动态性。
对于数组类而言,情况就有所不同,数组类本身不通过类加载器创建,他是由Java虚拟机直接在内存中动态构造出来的。但数组类与类加载仍然有很密切的关系,因为数组类的元素类型最终还是要靠类加载器来完成加载,一个数组类创建过程遵循以下规则。
如果数组的组件类型是引用类型,那就递归采用本节中定义的加载过程去加载这个组件类型。数组C将被标识在加载该组件类型的类加载器的类名称空间上。
如果数组的组件类型不是引用类型,Java虚拟机将会把数组C标记为与引导类加载起关联。
数组类的可访问性与它的组件类型的可访问性一直,如果组件类型不是引用类型,它的数组的可访问性将默认为public,可被所有的类和接口访问到。
加载阶段结束后,