JVM 5. class文件加载的过程
0.思考
如果让我们自己来加载一个类文件,我们会怎么做呢?
-
.class文件是什么? -
.class文件的结构? -
加载是要干什么?虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型 -
加载有哪些步骤 -
加载的各个步骤是在干什么,是在什么阶段运行?
1.Class 文件里面有什么?
现在我们还不知道类加载是干什么?
但我们猜测无非就是把.class 中描述类的信息导入的内存中去,或者JVM 中去。
那么我们先要知道Class文件中有哪些信息,才能对类加载的过程有更细致的了解。
2.什么是类的加载
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
那么显然
3.类加载的各个阶段
3.1 编译知识
为了方便后面的理解,这里补充一些编译的知识。
链接:其实就是一个“打包”的过程,它将所有二进制形式的目标文件和系统组件组合成一个可执行文件。完成链接的过程也需要一个特殊的软件,叫做链接器(Linker)。
对Java而言,程序的执行也是依靠于JVM的。
这里我们就能看出JVM的厉害之处,相较于传统的编译性语言,JVM 将 Class 文件加载后,连接阶段的执行都是动态的
准确的说是加载、验证、准备、初始化、和卸载这五个阶段的执行顺序时确定的,但连接的解析阶段的执行时间是不确定的,可以在初始化后再执行,这就是Java 语言的运行时绑定。后面会详细解释。
3.2 加载的时机
加载的时机 JVM 没有明确规定,但规定在以下5中情况下必须立即对类进行初始化,那么在这之前必定要进行类的 加载、验证、准备。
以上5种行为被成为对一个类的主动引用。除此之外,所有引用类的方式都不会触发初始化,被称为被动引用。
3.3 类加载的全过程
3.3.1 加载
在加载阶段,JVM完成下面三个操作:
这里着重解释下第一条:通过一个类的全限定名来获取定义此类的二进制字节流
这里并没有制定二进制字节流的来源。这点很重要,代表这我们不仅仅可以从Class文件中获取二进制字节流,还可以从
- ZIP、JAR、EAR、WAR等文件中读取
- 运行时计算生成如动态代理技术(对比字节码增强是在类通过ClassLoader加载类时,加载新的类)
完成了类加载的加载后,虚拟机外部的二进制字节流就按照JVM所需的格式存储在方法区中。然后在内存中实例化一个java.lang.Class 类的对象。
3.3.2 验证
首先我们要明白,加载完成的时候,验证已经进行了一部分了。如果加载完全完成,也就 java.lang,Class 类已经生成了,这时候再来验证不是已经晚了?
加载和验证是交叉执行的。
验证就是确定 Class文件中的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全。
显然我们写的通过了Java 编译器的代码生成的Class文件都是可以通过验证的。
但有些Class文件没有通过这些步骤,可能就不安全了。
验证是一个很长的过程,交叉执行于其他过程之中
验证的各个阶段很复杂,如果对整个类加载流程没有一定的了解(比如现在的我)好像看不懂,这里挖个坑,后面我看明白了再来填。。。。
3.3.3 准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区分配。(看到这句话的我懵了,不过别急,看后面解释)
首先准备阶段进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量。
设置初始值的意思是设置为系统的初始值,如0。而不是程序里面设置的初始值。
public static int value = 123;
在准备完成后,value的值为0,而不是123.
3.3.4 解析
将常量池中的符号引用解析为直接引用
简单理解:比如一个Class D,如果当前D这个类的Class文件还没被加载到内存中,但我有一个C的Class类,里面已经用到了Class D,这个时候 C里面D就会用一个能D.class文件对应符号代替。
当我去解析C的Class类时,里面的全部的符号引用就会被换为D的Class类的引用。那么显然如果D.class还没被加载进来,C的Class类在解析时也会去加载D.class文件。
可以类比对象实例的引用,我对那个引用赋值之前,他就是一个能确定引用类型的符号,解析之后,他就真真指向了一个对象实例。
解析还包括:
- 对类或接口的解析
- 对字段的解析
- 类方法的解析
- 接口方法的解析
这里不再做详细的解释。
3.3.5 初始化
从这里开始执行类中定义的Java程序代码
初始化就是通过搜集我们程序中写的各种各样的对类变量(静态变量)初始化程序,来生成一个< clinit >()方法 。
我们对类变量的赋值动作和静态代码块中的语句合并产生。静态代码块只能对定义在他之前的类变量操作,后面的则可以赋值,不能访问。
其次,父类的< clinit >()方法会在子类的< clinit >()方法执行前执行完。
3.4 类加载器
类加载器十分重要。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确认其在JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。
3.4.1 类加载器分类
3.4.2双亲委派机制
注意双亲委派机制一般不会以继承来实现,而是使用组合关系来复用父加载器的代码
注意这里的类加载,都是按照全类名去找自己所管辖的路径下有没有这个类,有就加载自己路径下的,没有就交给子加载器。
双亲委派机制一般不会以继承来实现,而是使用组合关系来复用父加载器的代码
注意这里的类加载,都是按照全类名去找自己所管辖的路径下有没有这个类,有就加载自己路径下的,没有就交给子加载器。
|