0%

JVM类加载机制

类加载全过程

当我们使用Java命令启动一个类的main()方法时,首先需要通过类加载器将主类加载到JVM中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.weiba.jvm;

public class Math {
public static final int initData = 666;
public static User user = new User();

public int compute() { //一个方法对应一块栈帧内存区域
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}

public static void main(String[] args) {
Math math = new Math();
math.compute();
}

}

通过Java命令执行代码的大体流程

loadClass 加载过程

加载

在硬盘上查找并通过IO读取字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等。在加载阶段会在宿主机内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

验证

效验字节码文件的的正确性,如class文件内容依次应该为:CAFEBABE(魔数),副版本号,主版本号,常量池计数器,常量池区域,类信息等等

Class文件结构参照表

准备

给类的静态变量分配内存,并赋予默认值。

静态变量:基础类型如int为0,boolean为false,对象为null。

静态常量:用字面量进行显示赋值,字面量(保存在常量池中) 可以认为是有确定值的基本数据类型,还有具有确定值的string。

解析

符号引用(静态方法如main())替换为直接引用(内存地址),该阶段会把一些静态方法替换为指向数据所存内存的指针或句柄。这就是所谓的静态链接(类加载期间完成)过程,动态链接是程序在运行期间将符号引用替换为直接引用

初始化

  • 对类的静态变量初始化为指定的值。
  • 执行静态代码块。

方法区

类被加载至方法区后主要包含:运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应Class实例的引用等信息。

类加载器的引用

这个类加载时使用的类加载器。

对应Class实例的引用

类加载器在加载类信息放到方法区后,会创建一个对应的Class类型的对象实例放到堆中,作为开发人员访问方法区中类定义的入口和切入点。

类加载器

注意:同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一样,所以看两个类对象是否是同一个,除了看类的包名和类名是否都相同之外,还需要他们的类加载器也是同一个才能认为他们是同一个。

引导类加载器(BootStrapClassLoader)

负责加载JRE下lib目录的核心类库。

扩展类加载器器(ExtClassLoader)

负责加载JRE下lib目录中ext扩展文件夹中的类库。

应用类加载器(APPClassLoader)

负责加载classPath路径下的类库,主要是加载我们直接写的和导入的依赖包。

自定义类加载器

自己定义的类加载器,自己管理,如Tomcat打破双亲委派的自定义类加载器,实现各个路径下的class隔离。

自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法

全盘负责委托机制

全盘负责委托机制是指当一个ClassLoader装载一个类时,除非显式的使用另一个ClassLoader,该类所有依赖的及引用的类都使用这个ClassLoader载入。

双亲委派机制

原理

一个类在加载时,会先找这个类加载器的父类,看是否为空,不为空继续它类加载器的父类,直到没有父类。在找到后会先判断有没有加载过这个,找到直接返回,没有找到返回子类加载器查找,直到返回到自己的类加载器,开始真正的加载

为什么要设计双亲委派机制?

  • 沙箱安全机制:自定义的java.lang.String.class类不会被加载,这样可以防止核心类库被篡改。
  • 防止类的重复加载:当父类已经加载了该类,子类就没有必要再加载一次了,保证被加载类的唯一性。

JVM级别类加载全过程