深入理解Java虚拟机学习笔记(五)

虚拟机类加载机制

类的生命周期

  • 加载
  • 验证
  • 准备
  • 解析
  • 初始化
  • 使用
  • 卸载

验证,准备,解析三个部分统称为连接。

类加载的过程

加载

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

验证

  1. 文件格式验证
  2. 元数据验证
  3. 字节码验证
  4. 符号引用验证

准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段。注意对于静态变量设置初始值不是设置类变量被赋予的值,基本数据类型为对应的零值,引用类型为null;对于常量(即带有final修饰符的静态变量)则会直接设置其被赋予的值。

解析

解析阶段是将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口,字段,类方法,接口方法,方法类型,方法句柄和调用点限定符7类符号引用进行。

  • 符号引用:符号引用以一组符号来描述所引用的模板,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关。
  • 直接引用:直接饮用可以是直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的。

初始化

初始化阶段是执行类构造器<clinit>()方法的过程(注意与<init>()方法区分)。

关于 <clinit>() 方法

  1. <clinit>()方法由所有类变量的赋值动作和静态语句块合并产生的,顺序由语句在源文件中出现的顺序所决定的,静态语句块只能访问在静态语句块之前的变量,定义在后面的变量,静态语句块内可以赋值但不能访问。
  2. 虚拟机会保证先调用父类的<clinit>()方法,所以在虚拟机中第一个被执行的<clinit>()方法的类是java.lang.Object,也意味着父类中定义的静态语句块要优先于子类。
  3. 接口也可能会生成<clinit>()方法,因为接口可能存在变量初始化的赋值操作。接口的<clinit>()方法不需要先执行父接口的<clinit>()方法,只有父接口中定义的变量被使用时,父接口才会初始化。
  4. <clinit>()方法对于类或接口不是必需的,如果他们没有静态语句块和静态变量的赋值操作,那么编译器可以不为他们生成<clinit>()方法。
  5. 虚拟机会保证<clinit>()方法是线程安全的。

类的主动引用

对于类的初始化阶段,虚拟机规范中严格规定了有且仅有以下5种情况必须立即对类进行初始化,这5种情况中的行为称为对一个类进行主动引用。除此以外,所有引用类的方法都不会触发初始化,称为被动引用。

  1. 遇到new, getstatic, putstaticinvokestatic这4条字节码指令时,如果类尚未进行过初始化,则先触发类的初始化。如使用new关键字实例化对象,读取或设置一个类的静态字段(非final),调用一个类的静态方法。
  2. 使用java.lang.reflect包方法对类进行反射调用时。如果该类尚未进行过初始化,则先触发类的初始化。
  3. 初始化一个类时,如果该类的父类尚未进行过初始化,则先触发其父类的初始化。
  4. 虚拟机启动时,主类会被首先初始化。
  5. 使用JDK 1.7以上的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStaticREF_putStatic, REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类尚未进行过初始化,则需要先触发类的初始化。

类加载器

类加载器用于实现类的加载动作。比较两个类是否相等,只有在由同一个类加载器加载的前提下才有意义,对于使用equals()方法,isAssignableFrom()方法,isInstance()方法和instanceof关键字的结果都有影响。

  • 启动类加载器(Bootstrap ClassLoader):负责加载存放在JAVA_HOME/lib目录中或被-Xbootclasspath参数所指定的路径的,并且被虚拟机识别的类库。
  • 扩展类加载器(Extension ClassLoader):负责加载JAVA_HOME/lib/ext目录中或者被java.ext.dirs系统变量锁指定的路径中的类库。
  • 应用程序类加载器(Application ClassLoader):负责加载用户类路径(ClassPath)上所指定的类库。

双亲委派模型

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里的父子关系一般是以组合关系而不是继承关系来实现。

双亲委派模型的工作过程: 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈无法完成加载请求时,子加载器才会尝试自己去加载。