二、类加载器、双亲委派机制
之前一些知识点补充
-
作用:加载类。
-
什么时候会加载类呢:new Student(); 在对一个类创建一个新的对象的时候就会加载。通过ClassLoader加载之后,该类会进入栈中,而相应的对象则会进入堆。
package jvm;
public class Car {
public static void main(String[] args) {
// 拿到Car的整个类
Class<Car> carClass = Car.class;
System.out.println(carClass);
// -------------------------------------------------------------------------------------------------
System.out.println("==============================");
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();
Car car4 = new Car();
System.out.println(car1.hashCode());
System.out.println(car2.hashCode());
System.out.println(car3.hashCode());
System.out.println(car4.hashCode());
/*
1163157884
1956725890
356573597
1735600054
*/
Class<? extends Car> aClass = car1.getClass();
Class<? extends Car> aClass1 = car2.getClass();
System.out.println(aClass.hashCode());
System.out.println(aClass1.hashCode());
/*
460141958
460141958
*/
}
}
用代码来演示一下:
- 可以看出同一个类创建出来的不同对象的地址都是不同的。
- 不同的对象都放在了堆中,里面存储了不同的属性值,然后在栈里面存放了一个指针,使用的时候在从栈里面获得该指针,然后指向堆里面获得相应的数据。使用完就从栈里面弹出,这也是为何栈里面不会有垃圾,而只有堆里面才会有垃圾。
- 不同的对象,在使用getClass语句的时候获得的类模板反而是一致的。注意这里只是类模板,而不是类加载器,如果想要获得类加载器的话需要获得相应的类模板然后使用getClassLoader方法。
类加载器
-
作用:加载class文件。
![]()
-
加载:指的是将类的class文件读入进内存中,并且为止创建一个java.lang.class对象,也就是说,当程序使用任何类的时候,都会为之创建一个java.lang.class对象。
-
链接:连接阶段负责把类的二进制数据合并到JRE中。合并的过程又分为了三个阶段:
- 验证:用于验证被加载的类是否正确,和其他的类协调一致。保证安全。比如说有数组验证检查等等。
- 文件格式验证:验证字节流是否符合规范。
- 元数据验证:堆字节码描述的信息进行语义分析。
- 字节码验证:最重要的验证环节,分析数据流和控制。确定语义是合法的。
- 符号引用验证:针对符号转换。
- 准备:为类的静态变量分配内存和初始值。
- 解析:将类的二进制数据中的符号引用替换成直接引用。说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。
- 验证:用于验证被加载的类是否正确,和其他的类协调一致。保证安全。比如说有数组验证检查等等。
-
初始化:为类的静态变量赋予正确的初始值。
双亲委派机制
双亲委派机制就是寻找相应Java类的一个具体的方式。其作用就是为了保证安全。
Java类加载器的种类
- 虚拟机自带的加载器
- 启动类(根)加载器 Bootstrap classLoader:加载Java的核心类库,该类库在java.lang包下,同时也构造了ExtClassLoader和AppClassloader
- 扩展类加载器 ExtClassLoader:加载核心类库的一些扩展jar包,一般在jre/lib/ext中
- 应用程序加载器 AppClassLoader:主要负责加载应用程序的主函数类
这些加载器的调用是有一定的顺序的,而这个顺序也被称作是双亲委派机制。
Java类加载器的顺序 双亲委派机制
上java源码,打开“java.lang”包下的ClassLoader类。然后将代码翻到loadClass方法:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
// -----??-----
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 首先,检查是否已经被类加载器加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 存在父加载器,递归的交由父加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 直到最上面的Bootstrap类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
由此可见其实整个加载过程是一个巡回的过程,首先在加载的时候会寻找AppClassLoader加载器,查看是否会加载过。如果没有加载过那么就依次往上送,一直找,直到BootStrap ClassLoader加载器。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。

也就是说:寻找一个类加载器的流程就是从上至下寻找,如果最高级BootStrap里面存在,那么就调用根加载器加载,然后依次往下。这个就类似于二叉树的中序遍历序列。
为什么说它可以保证安全呢?
- 当一个类需要加载的时候,如果BootStrapClassLoader中含有该类,那么就会直接加载该加载器中的。如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。
- 就算是之后你写了一个同样的java.lang.String类,也不会送入到类加载器中加载。这就是能够保证安全的原因所在。


浙公网安备 33010602011771号