Java类加载机制(类加载过程)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zgsxzdl/article/details/83422155

最近感觉只是没有系统化,从类加载开始把一些只是梳理一下。

什么是类加载机制

相同的内容不同的描述方向:

1. 偏实用

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

2. 偏定义

每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class”文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载

上述class文件到可以被虚拟机直接使用的过程就是类的加载过程。

类加载过程

类加载过程如下:

  1. 加载:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象
  2. 链接:把类的二进制数据合并到JRE中
    2.1 验证:检查载入Class文件数据的正确性
    2.2 准备:给类的静态变量分配存储空间,并赋初值
    2.2 解析:将常量池内的符号引用替换为直接引用的过程
  3. 初始化:对类的静态变量,静态代码块执行初始化操作

加载

通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象。

加载阶段,虚拟机需要完成3件事情:

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

这里有两个点:

  • 字节码来源
    虚拟机规范没有准确说明来源,可以通过自定义类加载器来控制字节流的获取方式。
    一般来源为:本地路径下编译生成的.class文件,jar包中的.class文件,从远程网络获取的.class文件,以及动态代理实时编译(TODO)
  • 类加载器
    包括启动类加载器,扩展类加载器,应用类加载器,自定义加载器。
    自定义加载器的作用:
    a. Java代码很容易反编译,可以对编译后的文件加密,然后使用自定义加载器解密后加载
    b. 从非标准的来源加载,比如网络来源。

TODO:
双亲委托,
自定义类加载器

验证

检查载入Class文件数据的正确性

目的在于保证.class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机自身安全。主要包含以下四种验证:
文件格式验证:比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息。
元数据验证:比如该类是否继承了被final修饰的类?类中的字段,方法是否和父类冲突?是否出现不合理的重载?
字节码验证:保证程序语义的合理性,比如要保证类型转换的合理性。
符号引用验证:比如校验符号引用中通过全限定名是否能找到对应的类?校验符号引用中的访问性(private,protect,public)是否可被当前类访问?

准备

给类的静态变量分配存储空间,并赋初值

这里的初值不是代码中写的初始化的值,而是Java虚拟机根据不同类型赋的默认初始值。比如基本类型的初值是0,引用类型的初值是null。有一个例外,常量的初值就是代码中设置的值。注意实例变量不会在这个阶段分配内存和初始化,类变量会分配在方法区中,而实例变量跟随对象一起分配到Java堆中。

final static int a = 8; // 准备阶段a的值就是8
static int b = 6; // 准备阶段b的值是0,到初始化阶段b才被赋值为6
int c = 2; // 准备和初始化阶段,c未被分配内存和初始化,等到实例化的时候才会被分配和初始化。

解析

将常量池内的符号引用替换为直接引用的过程

  1. 符号引用:一组符号来描述目标,可以是任何字面量。也就是一个字符串,这个字符串给出能够唯一标识一个类,一个方法,一个变量的信息。
  2. 直接引用:直接指向目标的指针,或一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针起到这个实例方法/变量的偏移量。

举例来说:

System.out.print("hello");

System.out.print方法的地址为123。那么System.out.print是符号引用,123是直接引用。

初始化

对类的静态变量,静态代码块执行初始化操作。

初始化顺序为定义的顺序。谁先定义谁先初始化。

这个阶段主要是对类变量初始化,是执行类构造器的过程。
初始化的时机:

  1. 遇到new, getstatic, putstatic, invokestatic四条指令的时,如果类没有进行初始化,需要触发其初始化。对应场景分别是:使用new关键字实例化对象,读取一个static变量,设置一个static变量的值,调用static方法。
  2. 使用java.lang.reflect包对类进行反射时,如果类没有初始化,需要触发其初始化
  3. 当初始化一个类时,发现其父类没有被初始化,需要先触发父类的初始化
  4. Java执行main方法的那个类会被触发初始化

TODO Java探针,在加载类的时候,改变字节码。

文章参考:
https://www.cnblogs.com/ITtangtang/p/3978102.html
https://blog.csdn.net/ln152315/article/details/79223441
https://blog.csdn.net/javazejian/article/details/73413292

这几个文章写得都很好,如有侵权问题,请及时联系删除。

猜你喜欢

转载自blog.csdn.net/zgsxzdl/article/details/83422155