Class 加载过程
承接上篇 Class 文件格式
- loading : 指.class文件加载到内存的过程
- verfication : 核实.class文件是否符合JVM规范
- preparation:给class静态变量赋默认值
- initializing:静态变量赋初始值
类加载器
JVM是通过类加载器(ClassLoader)加载 class 文件到内存的,包括以下两个步骤:
- 开辟内存区域存放.class二进制文件
- 生成一个Class类对象,这个Class对象指向了二进制文件
Java 中有不同类型的 ClassLoader ,它们负责加载不同类库的 class 文件,如上图
- BootstrapClassLoader
- ExtClassLoader
- AppClassLoader
看下以下例子:
public class ClassloaderTest {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(sun.awt.HKSCS.class.getClassLoader());
System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());
System.out.println(ClassloaderTest.class.getClassLoader());
}
}
执行结果:
null
null
sun.misc.Launcher$ExtClassLoader@5a07e868
sun.misc.Launcher$AppClassLoader@18b4aac2
String 处于 rt.jar 包下,HKSCS处于 charsets.jar 包下,它们都是由 BootstrapClassLoader 加载的,而BootstrapClassLoader 由c++ 实现,java 代码中并没有这个类,所以输出为null
ClassloaderTest 为自定义类,由 AppClassLoader 加载
类加载器关系
CustomClassLoader 父加载器为 AppClassLoader
AppClassLoader 父加载器为 ExtClassLoader
ExtClassLoader 父加载器为 BootstrapClassLoader
注意: AppClassLoader 不是继承自 ExtClassLoader ,而是 AppClassLoader 类中有一个 private final ClassLoader parent; 属性,它指向了ExtClassLoader,AppClassLoader、ExtClassLoader 都是继承自URLClassLoader
以下例子可以说明这一点:
public class ClassloaderTest2 {
public static void main(String[] args) {
System.out.println(ClassloaderTest2.class.getClassLoader());
System.out.println(ClassloaderTest2.class.getClassLoader().getParent());
System.out.println(ClassloaderTest2.class.getClassLoader().getParent().getParent());
}
}
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@7440e464
null
Class 文件加载过程
如下图,当使用自定义类加载器CustomClassLoader 加载class文件时,步骤如下:
- 首先在CustomClassLoader 缓存中查找,缓存中有直接返回结果,没有交给 AppClassLoader 处理
- AppClassLoader 同样在自己的缓存中查找是否已经加载,有则直接返回,没有就交给 ExtClassLoader 处理
- ExtClassLoader 同样在自己缓存中查找,有则返回,没有就交给BootstrapClassLoader处理
- BootstrapClassLoader 在自己缓存中查找,有则返回,没有则交给ExtClassLoader 处理
- ExtClassLoader 调用 自己的 findClass 方法,假设当前要加载的类为DNSNameService那么就加载成功了,否则,交给 AppClassLoader 处理
- AppClassLoader 调用自己的 findClass 方法,假设当前要加载的类为classpath路径下的class则加载成功,否则交给自定义加载器 CustomClassLoader 处理
- CustomClassLoader 调用实现的 findClass 方法
这样一个递归查找的过程称为 双亲委派机制
为什么使用双亲委派机制
主要出于安全考虑,例如假设有一个名为 java.lang.String 的自定义String类可以通过自定义的类加载器加载,从而达到替换官方string 的目的,这样就造成了安全问题。
而有了双亲委派机制,那么就首先会从父加载器中查找是否已经加载了名为 java.lang.String 的 class 文件,有则直接返回,不会走到自定义加载器
解释器与编译器
解释 “解释器” 和 “编译器” 前先要了解什么是解释执行,什么是编译执行:
- 解释执行:将.class 字节码文件逐句翻译成对应平台的机器指令并执行,不会产生机器码文件
- 编译执行:将.class文件编译成机器码后再执行
优缺点:
- 启动效率:解释执行不用编译,所以启动效率更高
- 运行效率:编译执行只需要编译一次,而解释执行每次都要经过编译,所以编译执行的运行效率更高
虚拟机执行模式
在部分的商用虚拟机(Sun HotSpot、IBM J9)中,Java程序最初是通过解释器 (Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把 这些代码认定为“热点代码”(Hot Spot Code)。为了提高热点代码的执行效率,在运行时, 虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个 任务的编译器称为即时编译器(Just In Time Compiler,下文中简称JIT编译器)。
(摘自深入理解Java虚拟机第2版)
通过 java -version 命令可以查看虚拟机的执行模式,默认为 mixed mode 混合模式
C:\Users\Administrator>java -version
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
为何使用混合模式
解释器与编译器两者各有优势:当 程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。 在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码 之后,可以获取更高的执行效率。当程序运行环境中内存资源限制较大(如部分嵌入式系统 中),可以使用解释执行节约内存,反之可以使用编译执行来提升效率。
如何更改执行模式
使用参数“-Xint”强制虚拟机运行 于“解释模式”(Interpreted Mode),这时编译器完全不介入工作,全部代码都使用解释方式执行
使用参数“-Xcomp”强制虚拟机运行于“编译模式”(Compiled Mode), 这时将优先采用编译方式执行程序