首先提到一个概念:类加载器(Class Loader)
我们在IDE中编写好的Java程序会被编译器编译成.class文件(字节码文件),然后通过类加载器来负责将这些.class文件加载到JVM中执行。
JVM中提供了三层的ClassLoader:
- Bootstrap ClassLoader:主要负责加载核心的类库(java.lang.*)等,构造ExtClassLoader和AppClassLoader。
- ExtClassLoader:主要负责加载jre/lib/ext目录下的扩展类。
- AppClassLoader:主要负责加载应用程序的主函数类。
那么,当我们写的Hello.java编译形成的Hello.class文件,又是如何被JVM所加载的呢?
首先打开java.lang包下的ClassLoader类,找到loadClass方法:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
//-------------------------------------------------------------------------
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 {
//不存在父加载器,使用启动类加载器 BootstrapClassLoader加载
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();
//自己尝试加载
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
下面的图描述了这个过程:
当一个Hello.class要被加载时,**在不考虑自定义类加载器的情况下,**首先会在AppClassLoader中检查是否已经被加载过了,如果没有被加载过,会交由上层父加载器进行处理,同样,父加载器也是先判断是否已经被加载过了,如果没有进一步交由上一层类加载器,直到BootstrapClassLoader,此时已经没有类加载器了,这时候考虑自己是否能够加载,如果无法加载,就会下沉交由子加载器,一直到最底层。如果到达最底部加载器都没办法加载,就会抛出ClassNotFoundException。
那么为什么要有这种机制呢?
首先来看这段程序:我们创建一个类String,和系统自带的String类同名同包,然后运行main方法,猜一猜会有什么效果?
package java.lang;
public class String {
@Override
public String toString() {
return "helloWorld";
}
public static void main(String[] args) {
String str = new String();
System.out.println(str.toString());
}
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-48G88M4t-1675132640203)(C:\Users\OYMN\AppData\Roaming\Typora\typora-user-images\image-20220221113551765.png)]
报错了?找不到main方法?,奇怪,main方法不都有吗。。。
这就涉及到这篇文章的重点了,双亲委派机制有什么用?
通过前面的知识,我们不妨来思考思考。这个String.java文件被编译成字节码文件后,首先会先进入AppClassLoader,然后一步一步交由上一层加载器,直到BootstrapClassLoader,它把这个类认为成了Java自带的String类,下面的类加载器也就没有机会再去加载了。因此自始自终JVM中存在的关于String的相关信息都是Java自带的那个,而不是我们自定义的,自带的String类自然没有main方法,所以程序才会报错。
这也说明了双亲委派机制的重要好处:如果有人想要替换系统级别的类,想要篡改它的实现,在这种机制下会先被BootstrapClassLoader加载,而BootstrapClassLoader加载的都是系统级别的类,自然会忽视我们自定义的类,从一定程度上防止了危险代码的植入。并且,也保证了Java的核心类不会被篡改,保证Java程序的稳定运行,例如上面我们自定义了一个java.lang.String类,那么程序运行时,系统就会出现不同的String类,产生安全隐患。