深度剖析JVM类加载机制
# 深度剖析JVM类加载机制
# 一、类加载运行全过程
当我们执行java com.ruanyou.Math
这样一个命令,整体过程大致如下:
1、windows系统下java.exe
会调用底层的jvm.dll
文件创建Java虚拟机(C++实现)
2、随后创建一个引导类加载器实例(C++实现)
3、C++调用Java代码创建JVM启动器实例sun.misc.Launcher
,该类由引导类加载器负责加载,会创建其它类加载器
//Launcher的构造方法
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//构造扩展类加载器,在构造的过程中将其父加载器设置为null
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader,
//Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自己写的应用程序
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
。。。 。。。 //省略一些不需关注代码
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
4、sun.misc.Launcher.getLauncher()
,获取运行类自己的加载器ClassLoader
,是AppClassLoader
的实例launcher.getClassLoader()
5、调用loadClass
加载要运行的类Math
classLoader.loadClass("com.ruanyou.Math")
6、类加载完成后,JVM会执行Math类的main方法
7、JVM销毁
# 二、类加载器类型
# 引导类加载器(BootstrapClassLoader
)
负责加载支撑JVM运行的位于JRE的lib
目录下的核心类库,比如rt.jar
、charsets.jar
等
# 扩展类加载器(ExtClassLoader
)
负责加载支撑JVM运行的位于JRE的lib
目录下的ext
扩展目录下的类库
# 应用程序类加载器(AppClassLoader
)
负责加载ClassPath
路径下的类包,主要就是加载自己写的那些类
# 自定义类加载器
如果想自定义类加载器,需要继承ClassLoader
,并重写findClass
方法,如果想打破双亲委派机制,还要重写loadClass
方法
# 三、类的加载过程
# 1、加载
在硬盘上查找并通过IO读入字节码文件,只有使用到类时才会加载,例如调用类的main()
方法,new
对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class
对象,作为方法区这个类的各种数据的访问入口
Math math = null,这种是不会加载类的
# 2、验证
校验字节码文件的正确性
# 3、准备
给类的静态变量分配内存,并赋予默认值
public class TestMain {
static {
System.out.println("1、静态变量a的值:" + TestMain.a);
}
private static int a = 10;
static {
System.out.println("2、静态变量a的值:" + TestMain.a);
a = 11;
}
static {
System.out.println("3、静态变量a的值:" + TestMain.a);
}
public static void main(String[] args) {
System.out.println("=========================");
}
}
输出:
1、静态变量a的值:0
2、静态变量a的值:10
3、静态变量a的值:11
=========================
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 4、解析
这个阶段会将符号引用(一些静态方法,比如main()方法等)替换为被加载到内存区域的代码的直接引用。也就是静态链接过程
# 5、初始化
1、将类的静态变量初始化为指定的值
2、执行静态代码块
1和2,此处不分先后,按照静态的顺序性来执行
# 四、类的加载机制
# 1、双亲委派机制
# 1.1 双亲委派机制原理
注意:并非继承,而是持有父加载器的引用
可以简单理解为:向上委托,向下加载
ClassLoader里面的loadClass方法,实现了双亲委派机制,整体过程大致如下:
1> 首先会检查当前类加载器是否已经加载了这个类,如果加载了,那么直接返回(就是调用findLoadedClass(name)
方法)
2> 如果没有加载过,那么再判断一下父加载器是否为空;如果父加载器不为空,那么委托父加载器加载这个类(就是调用parent.loadClass(name, false)
3> 如果父加载器为空,那么委托引导类加载器加载这个类(就是调用findBootstrapClassOrNull(name)
方法)
4> 如果父加载器以及引导类加载器都没有找到指定的类,那么调用当前类加载器的findClass
方法查找并加载这个类。
//ClassLoader的loadClass方法,里面实现了双亲委派机制
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 检查当前类加载器是否已经加载了该类
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) { //如果当前加载器父加载器不为空则委托父加载器加载该类
c = parent.loadClass(name, false);
} else { //如果当前加载器父加载器为空则委托引导类加载器加载该类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
// If still not found, then invoke findClass in order to find the class.
long t1 = System.nanoTime();
//都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类
c = findClass(name);
}
}
if (resolve) { //不会执行
resolveClass(c);
}
return c;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 1.2 为什么要设计双亲委派机制?
沙箱安全机制:
我们自己写的
java.lang.String.class
类不会被加载,这样可以防止核心API库被随意篡改 避免类的重复加载:
如果父加载器已经加载了这个类,子加载器就没有必要再加载,保证被加载类的唯一性
# 1.3 tomcat是如何打破双亲委派机制的?
tomcat
自定义了类加载器,继承ClassLoader
,并且重写了loadClass
和findClass
方法
# 2、全盘负责委托机制
当一个ClassLoder
装载一个类时,除非显示的使用另外一个ClassLoder
,否则这个类依赖以及引用的类也由这个ClassLoder
载入。
# 五、Java代码执行顺序
# 1、静态代码块、静态变量
用static修饰的处于同一优先级,同一优先级就按先后顺序来执行
static {
System.out.println(" 静态代码块A");
}
2
3
# 2、构造代码块
{
System.out.println("构造代码块A");
}
2
3
# 3、构造方法
public Test() {
System.out.println("构造方法");
}
2
3
- 规则一:静态优先且顺序性执行
- 规则二:父类优先于子类
- 规则三:构造代码块优先于构造方法
执行顺序:父类静态代码块 > 子类静态代块 > 父类构造代码块 > 父类构造方法 > 子类构造代码块 > 子类构造方法
代码示例一:
public class ClassA {
static {
System.out.println("父类静态代码块 ");
}
{
System.out.println("父类构造代码块 ");
}
public ClassA() {
System.out.println("父类构造函数 ");
}
}
public class ClassB extends ClassA{
static {
System.out.println("子类静态代码块 ");
}
{
System.out.println("子类构造代码块 ");
}
public ClassB() {
System.out.println("子类构造函数 ");
}
}
public static void main(String[] args) {
ClassB classB = new ClassB();
}
//执行结果
父类静态代码块
子类静态代码块
父类构造代码块
父类构造函数
子类构造代码块
子类构造函数
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
代码示例二:
在ClassB
中增加private static ClassB classB = new ClassB();
,此处子类静态代码块
在中间执行
public class ClassA {
static {
System.out.println("父类静态代码块 ");
}
{
System.out.println("父类构造代码块 ");
}
public ClassA() {
System.out.println("父类构造函数 ");
}
}
public class ClassB extends ClassA{
private static ClassB classB = new ClassB();//增加成员属性
static {
System.out.println("子类静态代码块 ");
}
{
System.out.println("子类构造代码块 ");
}
public ClassB() {
System.out.println("子类构造函数 ");
}
}
public static void main(String[] args) {
ClassB classB = new ClassB();
}
//执行结果
父类静态代码块
父类构造代码块
父类构造函数
子类构造代码块
子类构造函数
子类静态代码块
父类构造代码块
父类构造函数
子类构造代码块
子类构造函数
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39