1、JVM内存结构
要了解Java类的加载过程,我们必须先了解下JVM的体系结构,这里从网上找了几张图,整体结构如下:
2、类的加载介绍
类的加载:指的是将类的class文件转换成二进制文件读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.class对象,用来封装类在方法区内的数据结构。
类的加载的最终产品是位于堆区域的Class对象,Class对象封装了类在方法区内的数据结构,并且向程序员提供了访问方法区内的数据结构的接口。
上面两幅图就是JVM整体的一个架构了,今天这里说的java类加载过程,其实就是java类的生命周期,类的生命周期一共有五个阶段,分别是:
- 加载(Loading)
- 连接(Linking)
- 初始化(Initialization)
- 使用(Using)
- 卸载(Unloading)
其中这里第二步【连接】里面,可以细分为三个阶段
- 验证(Verification)
- 准备(Preparation)
- 解析(Resolution)
3、类加载过程
通过以上的说明,我们可以看出类的加载过程是发生在类装载器子系统中的。
类装载子系统: 虚拟机把描述类的数据从class文件加载到内存,并对数据进行验证、准备、解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
1、加载
加载其实就是类加载过程的第一个阶段,主要做了以下功能
- 通过一个类的全限定名来获取其定义的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在java堆区域生成一个代表这个类的java.lang.Class对象,作为方法区中这些数据的访问路口。
2、连接
第二部连接一般细分为三个小阶段,分别为验证、准备和解析。
1、验证
验证主要是为了确保被加载类的正确性
验证是链接阶段的第一步,这一阶段的目的是为了确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
- 文件格式验证:验证字节流是否符合class文件格式的规范。
- 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范要求;如这个类是否有父类,是否实现了父类的抽象方法,是否重写了父类的final方法等等。
- 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的,如:操作数栈的数据类型与指令代码序列能配合工作,保证方法中的数据类型转换有效等等。
- 符号引用验证:确保解析动作能正确执行;如:通过符合引用能找到对应的类和方法,符号引用中类、属性、方法的访问性是否能被当前类访问等等。
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
2、准备
为静态变量分配内存并且设置该静态变量的模式初始值。这些内存都将在方法区中分配。
- 只对static修饰的静态变量进行内存分配、赋默认值,如0、null、false等等
- 对final的静态字面值常量直接赋初值,(赋初值不是赋默认值,如果不是字面值静态常量,那么会和静态变量一样赋默认值)。
3、解析
将常量池内的符号引用解析为直接引用,指到内存中的具体地址。
- 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
3、初始化
为类的静态变量赋初值。
初始化阶段是执行类构造器< clinit >()方法的过程。类构造器< clinit >()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。也就是说,当我们代码中包含static变量的时候,就会有。
-
< clinit > ( )方法;如果当前类不存在static变量,那么它的字节码文件是不会存在< clinit >( )
-
< clinit >( )方法中的指令按语句在源文件中出现的顺序执行
-
< clinit >( )不同于类的构造器(构造方法)。(关联:构造器是虚拟机视角下的< init >( ))若该类具有父类,JVM会保证子类的< clinit >( )执行前,父类的< clinit >( )已经执行完毕虚拟机会保证一个类的< clini t>( )方法在多线程环境中被正确加锁和同步。
4、总结
这里用语雀画了一个思维导图,总结下整个过程