但行好事 莫问前程

【jvm】类加载机制

陈明羽 2019-09-23

微信搜索“我是树懒”或扫下方二维码关注公众号

优质文章,第一时间送达

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

类的生命周期

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

加载,验证,准备,初始化和卸载这5个阶段的顺序是固定的,为了支持动态绑定,解析这个过程可以发生在初始化阶段之后

加载阶段

加载阶段,主要完成以下3件事情:

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

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式有虚拟机实现自定义

验证阶段

验证阶段是为了确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全

  1. 文件格式验证

    基于字节流验证,验证字节流是否符合Class文件格式的规范,并且能被当前虚拟机处理

  2. 元数据验证

    基于方法区的存储结构验证,对字节码描述信息进行语义验证

  3. 字节码验证

    基于方法区的存储结构验证,进行数据流和控制流的验证

  4. 符号引用验证

    基于方法区的存储结构验证,发生在解析中,是否可以将符号引用成功解析为直接引用

准备阶段

准备阶段是正是为类变量分配内存并设置类变量初始值的阶段

// 初始值为0
public static int value = 123;
// final修饰,赋值为123
public static final int value = 123;

解析阶段

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

  • 符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。

  • 直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在

主要有四种:类或接口的解析,字段解析,类方法解析,接口方法解析

初始化

初始化阶段做的事就是执行类的初始化方法。方法是由编译器自动生成的。就是将static变量赋值指定的值并且static静态代码块中赋值指定值

java中,对于初始化阶段,有且只有以下五种情况才会对要求类立刻初始化:

  1. 使用new关键字实例化对象、访问或者设置一个类的静态字段(被final修饰、编译器优化时已经放入常量池的例外)、调用类方法,都会初始化该静态字段或者静态方法所在的类。
  2. 初始化类的时候,如果其父类没有被初始化过,则要先触发其父类初始化。
  3. 使用java.lang.reflect包的方法进行反射调用的时候,如果类没有被初始化,则要先初始化。
  4. 虚拟机启动时,用户会先初始化要执行的主类(含有main方法)
  5. jdk 1.7后,如果java.lang.invoke.MethodHandle的实例最后对应的解析结果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄,并且这个方法所在类没有初始化,则先初始化

注意:

  1. 父类定义的静态代码块优先于子类的静态代码块先执行
  2. 子类引用父类的静态字段不会被初始化
  3. 前面我们提到过final修饰的static变量在准备阶段就在方法区上赋值了,在解析阶段中实际上方法什么都不用做。final static 变量在编译期就完成了在元空间的赋值。而static 变量在准备阶段为默认值,初始化阶段进行赋值

类加载器

类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载如JVM中,同一个类就不会被再次载入了

比较两个类是否相等,只有在这两个类是由同一个类加载其加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类就必定不相等

java提供的类加载器主要有下面三个:

  1. Bootstrap ClassLoader

    用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader

  2. Extensions ClassLoader

    用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类

  3. Application ClassLoade

    根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它

除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,来满足一些特殊的需求

双亲委派模型

下图为类加载器的双亲委派模型:

双亲委派模型并不是一个强制性的约束模型,而是java设计者推荐给开发者的类加载器实现方式

双亲委派模型的工作过程:

​ 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载

优势:

​ 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载。其次是考虑到安全因素,java核心api中定义类型不会被随意替换

参考:

https://www.ibm.com/developerworks/cn/java/j-lo-classloader/

https://blog.csdn.net/m0_38075425/article/details/81627349

使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章