JVM之类加载机制

121 阅读6分钟

1.1什么是类加载机制?

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,即Java.lang.Class。

1.2类的生命周期有几个阶段?

加载,验证,准备,解析,初始化,使用和卸载七个阶段,其中验证,准备,解析三个部分统称为连接。

78b6e95809f06e9617801826551c18f3.png

上图中,加载,验证,准备,初始化,卸载这五个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始。

关于在什么情况下需要开始类加载过程的第一个阶段“加载”,《Java虚拟机规范》中并没有进行强制约束。但是对于初始化阶段,《Java虚拟机规范》则是严格规定了有且只有六种情况必须立即对类进行“初始化”(而加载,验证,准备自然需要再此之前开始)

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

     1)使用new关键字实例化对象的时候
     2)读取或设置一个类型的静态字段(被final修饰,已在编译期把结果放入常量池的静态字段除外)的时候
     3)调用一个类型的静态方法的时候
    

2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化阶段。

3.当初始化一个类的时候,如果发现父类还没有初始化,则需要先触发其父类的初始化。

通过子类调用父类的静态字段,不会导致子类初始化

4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类。

5.当使用JDK1.7的动态语言支持的时候,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个句柄所对应的类没有进行过初始化,则需要先触发其初始化。

6.当一个接口中定义了JDK8新加入的默认方法(default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

1.3加载过程的执行动作?

1.通过类的全限定名来获取定义这个类的二进制字节流。

2.将这个字节流所代表的静态存储结构转化成方法区的运行时数据结构。

3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

相对于类加载过程的其他阶段,加载过程中的第一步(获取类的二进制字节流的动作)是开发人员可控性最强的阶段,因为并没有指明二进制字节流从哪里获取,如何获取。例如:

1.从ZIP压缩包中读取,最终成为JAR,EAR,WAR格式的基础

2.从网络中获取,这种场景的典型应用就是Web Applet

3.运行时动态生成,这种场景使用的最多的就是动态代理技术

4.尤其他文件生成,典型场景是JSP应用,由JSP文件生成对应的Class文件

5.从数据库中读取,较为少见

6.可以从加密文件中获取,典型的防Class文件被反编译的保护措施

1.4验证

1.4.1文件格式验证

  • 是否以16进制0XCAFEBABE开头
  • 版本号是否正确
  • ......

第一阶段主要目的是保证输入的字节流能正确地解析并存储于方法区内,格式上符合描述一个Java类型信息地要求。

1.4.2元数据验证

  • 是否有父类
  • 是否继承了final类(因为final类不能被继承)
  • 一个非抽象类是否实现了所有方法
  • ......

第二阶段的主要目的是对类的元数据信息进行语义校验,保证不存在与《Java语言规范》定义相悖的元数据信息。

1.4.3字节码验证

  • 任何跳转指令都不会跳转到方法体以外的字节码指令上
  • 栈的数据类型和指令代码吻合,比如不会出现“在操作栈位置放了一个int型数据,使用时却按long类型来加载入本地变量表中”
  • ......

第三阶段较为复杂,主要目的是通过数据流分析和控制流分析,确定程序语义是合法的,符合逻辑的。

1.4.4符号引用验证

  • 符号引用中通过字符串描述的全限定名是否能找到对应的类
  • 符号引用中的类,字段,方法的可访问性是否可被当前类访问
  • ......

第四阶段校验行为发生在虚拟机将符号引用转化为直接引用的时候(解析阶段),主要目的是确保解析行为能正常执行,若无法通过符号引用验证,虚拟机会抛出一个java.lang.IncompaibleClassChangeError的子类异常,典型的如:IllegalAccessError,NoSuchFieldError,NoSuchMethodError。

在生产环境的实施阶段可以考虑使用 -Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类的加载的时间。

1.5准备

准备阶段是正式为类中定义的变量(即静态变量)分配内存并设置类变量初始指的阶段。 默认值如下:

a96ec5dd0018f7d079d8847d9a26eed2.png

若该变量有final修饰,如:

public static final int value = 123;

那么编译时Javac会为value生成ConstantValue属性,在准备阶段就会根据ConstantValue的设置将value赋值为123。

1.6解析

解析阶段时Java虚拟机将常量池内的符号引用替换为直接引用的过程。

符号引用:用一组符号来描述所引用的目标,可以是任何字面量。

直接引用:直接指向目标的指针,相对偏移量或一个能间接定位到目标的句柄。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。

1.7初始化

执行类构造器<clinit>()方法的过程。

在准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,比如赋值。