Java - Java类与类加载器

类加载机制

类加载机制个人认为是JVM中比较重要的一部分,因此在JVM系统学习之前就先学习了类加载机制的相关细节,以记之。

阶段

image-20190327204927833

其中解析可能会发生在初始化之后,使用可能不会被使用。

上述流程指的是开始时间的顺序,比如说加载未结束可能验证就会开始

类加载时机

虚拟机严格规定了5种情况必须立即对类进行初始化(不是上述流程中的初始化,指的是初始化类对象):

  1. 遇到newgetstaticputstaticinvokestatic这4条字节码指令时,如果类没有进行初始化则需要先触发其初始化。

  2. 对类进行反射调用;

  3. 当初始化一个类时,若父类还没有被初始化需要先触发其父类的初始化;
  4. 当虚拟机启动时,包含main()方法的那个类需要被初始化;
  5. 当使用动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStaticREF_putStaticREF_invokeStatic的方法句柄,并且句柄对应的类没有被初始化。

不会触发类的初始化的可能操作

  • 通过子类调用父类的静态字段,不会导致子类初始化
  • 通过数组定义来引用类,不会触发该类的初始化
  • 引用类的静态常量域或字段,不会导致该类的初始化

注意,接口也是会有初始化的过程,与类唯一不同的是上述第3点:接口在初始化时,并不要求其父接口全部都完成了初始化(原因应该是接口<clinit>()方法不需要调用父类的<clinit>()方法),只有在真正使用到父接口的时候(如引用接口中定义的常量时)才会初始化。

加载

加载阶段的3件事情:

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

第1件事情中的二进制字节流不一定是本地文件,可能是从ZIP获取从网络获取(Applet)动态代理JSP生成数据库读取等。

验证

验证主要是为了虚拟机对自身保护的一项重要工作,大致会完成以下4个阶段的检验动作:

  1. 文件格式验证:检测字节流是否符合Class文件格式规范
  2. 元数据验证:语义分析,保证信息符合Java语言规范的要求,主要是数据类型
  3. 字节码验证:最复杂的一部分,主要是对类的方法体进行校验(控制流、跳转等)
  4. 符号引用验证:发生在解析阶段,主要是对符号引用进行匹配性校验(能否找到、是否可达等)

准备

准备阶段是为类变量(静态变量)分配初始值的过程。

注意两点:

  1. 初始值通常情况下是数据类型的零值,比如语句public static int value = 123;会在准备阶段给value初始化为int的零值即0,而123会在后续的初始化阶段被赋值value
  2. 特殊情况下,常量类型会在准备阶段被赋值,比如语句public static final int value = 123;

解析

解析阶段是将常量池内的符号引用替换为直接引用的过程

符号引用

是指以一组符号来描述所引用的目标,符号引用在使用时能无歧义地定位到目标。

直接引用

是指可以直接指向目标的指针相对偏移量一个句柄

初始化

正式开始执行类中定义的Java代码(或者说是字节码)。记得准备阶段有为变量赋予初始值,这里就会为其赋予程序中制定的初始值。

初始化主要的过程是执行<clinit>()方法。

类与类加载器

对于任意一个类,都需要由加载它的类加载器类本身一同确立其在JVM中的唯一性。

在使用instanceof关键字、Class对象的equal()isAssignableFrom()isInstance()方法时,都需要判定上述两方面是否相等。自定义的类加载器系统自带的类加载器加载的同一个类生成的对象使用相等方法验证是得不到相等结果的

双亲委派模型

类加载器划分:

  • 启动类加载器:负责将<JAVA_HOME>\lib目录下的能被虚拟机识别的类库加载到虚拟机内存中,程序无法直接引用。
  • 扩展类加载器:负责将<JAVA_HOME>\lib\ext目录下的能被虚拟机识别的类库加载到虚拟机内存中,程序可直接使用。
  • 应用程序类加载器:负责加载用户类路径(ClassPath)上的类库,程序可直接使用。

双亲委派模型如下图所示:

image-20190327234320380

其中每一层与其父层关系一般不是继承(Inheritance)而是组合(Composition)来复用父加载器的代码。

工作过程:如果一个类加载器收到了类加载的请求,它首先不会尝试加载这个类,而是把这个请求委派给父类加载器去加载每个层次都是这样,直到请求被传递到顶层的启动类加载器中;而只有父加载器反馈自己无法完成此请求时,子加载器才回去尝试加载

双亲委托模型在ClassLoader类中的loadClass()方法中实现。

Rui wechat
我们梦中见
哟,小老板