JVM类加载机制

时间:2024-01-25 09:04:47

描述JVM如何加载Class字节码文件。

类加载过程

  • 加载
  • 连接
    • 验证
    • 准备
    • 解析
  • 初始化


加载

  • 获取类的二进制字节流加载到内存(比如从Zip包,网络,反射中读取)
  • 将字节码的静态数据结构转换成运行时数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口


验证

保证字节流的信息符合JVM规范。(JVM的自我保护机制)

正常运行Java程序可以通过.java编译成class文件,然后交由JVM执行。编译器虽然本身可以检测Java的安全问题。但是除了编译产生字节码文件之外,还可以通过其他途径产生,比如直接编写字节码文件或者通过第三方无编译检查的编译器生成。


JVM验证包括四个方面:

  • 文件格式验证

    版本号是否能被当前版本的虚拟机执行

    检查字节流是否有被删除或者附加的信息

    ....

  • 元数据验证

    检测当前类是否有父类,是否继承了final修饰的类,是否重写了final修饰的方法

  • 字节码验证

    主要对方法体进行验证,避免由于方法运行时造成虚拟机崩溃。

  • 符合引用验证

    发生在符合引用转换为直接引用的时候。

    符号引用于直接引用的区别:符号引用可以理解为一个字符串,是静态的,也就是在程序未执行之前对类,方法等的表示。等程序执行的时候,会在内存中将符号引用转换为直接引用,真正的执行方法,完成类之间的调用。【举个栗子】我是特种兵系列一度热播,其中集训时候的编号就和直接引用很类似。参加集训之前每个人都有一个自己的名字,但是为了方便管理,集训的时候就会把名字和编号进行一个映射。使用编号更便于管理和统计。自己的名字就是一个符号引用,集训时候的编号就是一个直接引用。


准备

为类变量分配内存。

类变量:static修饰的静态变量


解析

将常量池中的符号引用替换成直接引用。发生时间不可预料,有可能和初始化阶段互相交换位置。


初始化

  • 为类的静态变量赋予正确的初始值
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。(main所在类)
  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。


类加载器


启动(Bootstrap)类加载器

  • 启动类加载器主要加载的是JVM自身需要的类,它负责将 /lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中
  • 这个类加载使用C++语言实现的。


扩展(Extension)类加载器

  • 由Java语言实现的
  • 负责加载/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。


系统(System)类加载器

  • 它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。


双亲委派模型

1567497541065

双亲委派模型的流程:

  • 当一个类加载器收到类加载的请求,首先会把请求委派给父类加载器去加载,因此最终的请求都会发给启动类加载器(Bootstrap ClassLoader)。
  • 当父类无法加载这个请求(在自己的搜索范围类无法找到所需的类),就会让子类自己去加载。

双亲委派模型的好处:

双亲委派模型的本质目的是为了避免类的重复加载,用代码类比的话,是为了实现代码复用。

【举个例子】每个类都有一个共同的父类Object,每个类在被加载时都会先去加载Object类,按照双亲委派模型的思路,所有的类都会优先被启动类加载器加载,那么也就是说只需要加载一次Object,当其他类需要Object时,直接返回已经加载过的Object.class。