在讲JVM类的加载过程之前,我们要先来看一看什么是类的加载?什么是类的加载器,类的加载器有哪些?最后来看一看类的加载过程?
类的加载:
定义:把类的.class文件加载到内存中的过程,就叫做类的加载。
我们可以看一下这幅架构图,红色的向下箭头就表示类的加载。这就是把类的.class文件加载到JVM内存中。
类的加载器:
背景:
前面我们看了类的加载是什么。那么在JVM中,我们要把文件加载到内存中需要怎么操作呢?这时类加载器便起作用了。
定义:
类加载器是把类的.class文件加载到内存中的工具。
种类:
在JDK8中有4种类型的类加载器
- 启动类加载器
- 扩展类加载器
- 应用程序类加载器
- 自定义类加载器

双亲委派原则:
定义:
类加载器加载类要遵循的规则,称为双亲委派原则。
解释:
说说什么双亲委派原则是什么。就是如果有一个类被需要加载,这个类先会被提交给最底层的自定义类加载器,而自定义加载器会把这个类提交给上级应用程序类加载器,同理...最后这个类会被提交给启动类加载器。当这个提交过程结束后会开始加载。启动类加载器会先尝试去加载这个类,如果加载失败,会把这个类提交给下级进行加载,同理....如果加载成功,将不会提交。
具体案例:
1.如果现在有一个String类需要加载,我们都知道String类是lib中的类。String类会先提交给最底层的自定义类加载器,而自定义加载器会把这个类提交给上级应用程序类加载器,同理...最后这个类会被提交给启动类加载器。当这个提交过程结束后会开始加载。启动类加载器会先尝试去加载这个类,加载成功, 不在向下提交。
2.如果现在有一个自定义Student类需要加载。Student类会先提交给最底层的自定义类加载器,而自定义加载器会把这个类提交给上级应用程序类加载器,同理...最后这个类会被提交给启动类加载器。当这个提交过程结束后会开始加载。启动类加载器会先尝试去加载这个类,加载失败,向下提交,直到提交给Application ClassLoader才加载成功。
图解:

好处:
1.避免重复加载
采用双亲委派模式的是好处是Java类加载带有优先级的层次关系当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,达到避免重复加载的作用。
2.避免核心类篡改
考虑到安全因素,java的核心api不能被篡改。假设通过网络传递一个名为java.lang.String的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.String,而直接返回已加载过的String.class,这样便可以防止核心API库被随意篡改。
破坏双亲委派原则:
深度知识,请自行了解。这里写只是使知识结构化。
什么地方违反了双亲委派模型?
https://www.sohu.com/a/334000357_505800
类的加载过程:
类的加载过程大致由3个大步骤组成:
如图:

上图中是类的生命周期,相比类的加载过程还多了使用和卸载两个过程。但是我们主要是研究类的加载,所以对于使用和卸载下面不做提及。
加载:
加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
注意:类加载器通常无须等到“首次使用”该类时才加载该类,有的类会预先加载,有的类会使用才加载。
链接(连接):
连接主要是将已经读到内存的类的二进制数据合并到虚拟机的运行时环境中去。
由如下三个过程组成:
验证是确保class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全,起到一种保护作用。
类变量(static变量)分配内存并设置类变量初始值的阶段,这里并不包括实例变量,实例变量是在对象实例化时随对象一起分配在java堆中。
将类的二进制数据中的符号引用替换成直接引用。符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行,布局和内存无关。直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄,该引用是和内存中的布局有关的,并且一定加载进来的。换句话说就是把逻辑引用转换为实际的物理内存引用。
初始化:
到此阶段,真正开始执行类中定义的Java程序代码。在准备阶段,类变量被赋过默认初始值,基本数据类型为0,对象类型为Null。而在初始化阶段,则是指定代码中确定的数据。或者更直接地说:初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>()方法时由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句。
|