25

JVM类加载机制小结

 4 years ago
source link: http://www.cnblogs.com/fly-bryant/p/13235842.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

这篇文章我们关注一个问题:Java程序是怎么进入JVM并执行的? 经常写Java程序的小伙伴应该都听说过类加载机制,在《深入理解Java虚拟机》里周老师已经讲的很清楚了,这篇随笔把之前的笔记以及一些总结重新梳理一下。前面我们已经知道 .java文件经过编译后变成Class文件,JVM加载的是字节码文件。这其中的细节不知道小伙伴们有么有了解过?

类从被JVM加载到内存开始,到卸载出内存为止,整个生命周期包括7个阶段,加载、验证、准备、初始化、卸载这个5个步骤顺序是确定的。

QbEfm2B.png!web

  • 加载,类的加载时机由JVM自行决定,JVM需要完成3件事情:
    • 通过类的全限定名获取类的二进制字节流
    • 将类的静态存储结构转化为方法区的运行时数据结构
    • 在内存中生成类的Class对象,作为方法区数据的入口

数组在Java中是一种引用类型,数组类的加载由Java虚拟机直接创建。类加载完成后,二进制字节流存储在方法区中,HotSpot虚拟机会创建一个java.lang.Class对象作为程序访问方法区中的数据的外部接口,这个Class对象存储在方法区中,加载阶段何连接部分阶段是交叉进行的。

  • 验证是连接部分的第一步,验证阶段是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。因为只有在字节码层面才能保证Java语言的相对安全性。验证阶段会大致完成4个阶段的检验动作:
    • 文件格式验证:验证 字节流 是否符合Class文件格式的规范,并且能被当前版本的JVM处理。只有验证通过了,字节流才会进入方法区存储。
    • 元数据验证:比如类是否有父类,类是否继承了不能被继承的类等,保证不存在不符合Java语言规范的元数据信息。
    • 字节码验证:对类的方法体进行校验分析
    • 符号引用验证:对常量池中的各种符号引用进行校验
  • 准备:为类变量分匹内存并设置类型变量的零值阶段。类变量指的是被static修饰的变量,不包括实例变量。例如变量i,在准备阶段值就是0L,只有在执行方法的时候值才会变成1。
private static long a = 1;
  • 解析:JVM将常量池内的符号引用替换为直接引用的过程。这里要解释下符号引用何直接引用。
    • 符号引用: 用一组符号描述所引用的目标,引用的目标不一定已经加载到内存中。
    • 直接引用:直接指向目标的指针、相对偏移量、间接定位到目标的句柄,直接引用的目标已经在内存中。
  • 初始化,类加载过程的最后一步,执行类构造器 <clinit> ()方也就是真正执行类中定义的Java程序代码。遇到使用new关键字实例化对象、读取或设置一个类的静态字段、调用一个类的静态方法这些场景时,如果类没有被初始化,使用java.lang.reflect包的方法对类进行 反射调用时,那么一定要先初始化。还有一种场景是初始化子类时,如果父类没有被初始化,那么要先初始化父类。再看下接口的初始化,与类不同的地方就在于父类的初始化,子接口在使用到父接口的时候才初始化。 在多线程环境中,如果多个线程同时初始化一个类,那么只有线程执行类的 <clinit> ()方法。

JVM加载字节码文件靠的是类加载器,这个操作是在JVM外部实现的。这样应用程序就可以自己决定如何获取所需的类。如果两个类来自同一个Class文件,但是由不同的类加载器加载,那么者两个类一定是不相等。从JVM角度讲,只有两种加载器。一种是启动类加载器,是虚拟机自身的一部分,由C++语言实现;还有就是其他类加载器,由Java语言实现,全都继承自抽象类java.lang.ClassLoader 独立于虚拟机外部。

从开发角度看,主要分为这三种:

  • 启动类加载器(Bootstrap ClassLoader):加载 <JAVA_HOME>\lib 目录中,或者被 -Xbootclasspath 参数所指定的路径中。
  • 扩展类加载器(Extension ClassLoader):主要加载 <JAVA_HOME>\lib\ext 目录中的
  • 应用程序类加载器(Application ClassLoader):加载用户 类路径(ClassPath) 上所指定的类库。如果我们没有自定义过自己的类加载器,那么这就是程序默认的类加载器。

这些类加载器之间的关系为:

qUVFR3Q.png!web

在使用类加载器加载类的过程种,最好遵循 双亲委派模型 。双亲委派的原理是:类加载器收到加载类的请求时,先把这个请求委派给父类加载去完成,每一层次的加载器按这个这个逻辑执行。那么所有的加载请求最终都应该传送到顶层的启动类加载中。父加载器无法加载,子加载器才会自己加载。这样做的好处是可以避免类的重复加载,保证程序运行的稳定性。

我们可以自定义类加载器,总结起来就是:(1)类继承ClassLoader (2) 重写findClass() 方法 (3) 调用defineClass()方法。在loadClass()里如果父类加载失败,调用findClass()方法加载,这样还是符合双亲委派机制的。破坏会双亲委派需要重写loadClass()方法。

参考资料:《深入理解Java虚拟机》第二版 周志明

《深入拆解Java虚拟机》郑雨迪


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK