JVM 学习笔记一 :JVM类加载机制
前言:
最近在看JVM相关资料,这里记录下学习笔记,希望自己能坚持学完,打牢基础。
一、类加载过程
1,类从被加载到JVM中开始,到卸载为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。
2,其中类加载过程包括加载、验证、准备、解析和初始化五个阶段。
二、Java类运行过程
我们编写完的程序都是以java结尾的文件,编译写完的代码都会生成一些后缀为class的字节码文件。
当执行java -jar命令后(以springboot为例),此时会启动一个JVM进程,然后通过类加载机制来将所需要的类加载到JVM中进行运行。
这样一个编译好的系统就可以运行起来了。
三、类加载机制
我们都知道类的加载是一个很繁琐的过程,基本流程如下:
加载-》验证-》准备-》解析-》初始化-》使用-》卸载
1、加载
到底什么时候该去加载一个类呢?答案就是在我们使用一个类的时候,以main() 函数为入口,当JVM启动的时候,它会将含有main()函数入口的代码加载然后开始执行。
首先是代码中含有main()函数,然后开始执行main()函数中的代码,接着遇到ReplicaMananger类,此时就会将对应的.class字节码文件加载到内存中。
2、验证|准备|解析
(1)验证阶段
简单来说,这一步就是根据Java虚拟机规范,来校验你加载进来的”.class”文件中的内容,是否符合指定的规范。
所以把”.class”加载到内存里之后,必须先验证一下,校验他必须完全符合JVM规范,后续才能交给JVM来运行。
(2)准备阶段
例如下面的一个”ReplicaMananger”类:
public class ReplicaMananger { Public static int flushInterval; }
假如有上面的一个类,它的class文件内容刚被加载到内存之后,会进行验证,确认这个字节码文件的内容是规范的,接着进行准备工作。
准备工作其实就是为”ReplicaMananger”类分配一定的内存空间,然后给它里面的变量分配内存空间,来一个默认的初始值。
比如上面的示例中,就会给”flushInterval"这个变量分配内存空间,给一个”0”这个初始值。
(3)解析阶段
解析阶段,实际上就是把符号应用替换为直接引用的过程。
在程序编译时,java类并不知道所引用的类的实际地址,因此只能引用符号引用来替代。例如,在Class文件中它以class_info、fieldref_info、methodref_info等类型的常量出现。
比如org.simple.People类引用了org.simple.Language类,在编译时,People类并不知道Language类的地址,因此只能使用org.simple.Language(假设是这个,当然实际是由类似于CONSTANT_Class_info的常量来表示的)来标识Language类的地址。直接引用可以是直接指向目标的指针、相对偏移量、一个能间接定位到目标的句柄。
3、核心阶段:初始化
上面说过,在准备阶段,就会给”ReplicaMananger”类给分配好内存空间,另外他的一个默认的初始值为”0”,那么在接下来,在初始化阶段,就会正式执行我们的类初始化代码了。
如上所示,flushInterval这个变量是由Configuration.getInt("replica.flush.interval")这段代码来获取一个值,并且赋值给他的。另外还比如在static静态代码块,也会在这个阶段来执行。
4、总结
一般来说,在new Class() 来初始化类对象时,会触发类的加载到初始化的全过程,把这个类准备好,然后是实例化一个对象出来。
或者是包含main()方法的主类,必须是立马初始化的。
此外,还有一个非常重要的规则,就是如果初始化一个类的时候,发现他的父类还没有初始化,那么必须先初始化他的父类。
四、双亲委托原则
Java中的类加载器
1,启动器加载器:Bootstrap ClassLoader
一旦JVM启动,那么首先就会依托启动类加载器,去加载Java安装目录下的”lib”目录下的核心类库。
2,扩展类加载器: Extension ClassLoader
这个类加载器也是类似的,就是Java安装目录下,有一个”lib\ext”目录,这里有一些类,就是需要使用这个类加载器来加载的。
3,应用程序内加载器:Application ClassLoader
这类加载器就是负责加载ClassPath环境变量中所指定路径中的类,可以理解为加载我们写好的Java代码。这个类加载器就负责加载我们写好的类到内存中。
4,自定义类加载器
除了上面几种,我们还可以自定义类加载器,根据自己的需求加载需要的类。
5,双亲委托机制
JVM类加载器有亲子层级结构的,如下图:
基于这个亲子层级结构,就有一个双亲委派的机制,也就是当我们加载一个类的时候,先找父亲去加载,不行的话再由儿子来加载。这样的话 可以避免多层级的加载器结构重复加载某些类。
6,双亲委派模式的优点:
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。