为什么要使用双亲委派机制?
java虚拟机只会在不同的类的类名相同且加载该类的加载器均相同的情况下才会判定这是一个类。如果没有双亲委派机制,同一个类可能就会被多个类加载器加载,如此类就可能会被识别为两个不同的类,相互赋值时问题就会出现。
双亲委派机制能够保证多加载器加载某个类时,最终都是由一个加载器加载,确保最终加载结果相同。
没有双亲委派模型,让所有类加载器自行加载的话,假如用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,系统就会出现多个不同的Object类, Java类型体系中基础行为就无法保证,应用程序就会变得一片混乱。
在介绍双亲委派机制的时候,不得不提ClassLoader(类加载器)。说ClassLoader之前,我们得先了解下Java的基本知识。
Java是运行在Java的虚拟机(JVM)中的,但是它是如何运行在JVM中了呢?我们在IDE中编写的Java源代码被编译器编译成.class的字节码文件。然后由我们得ClassLoader负责将这些class文件给加载到JVM中去执行。
JVM中提供了三层的ClassLoader:
Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
AppClassLoader:主要负责加载应用程序的主函数类
那如果有一个我们写的Hello.java编译成的Hello.class文件,它是如何被加载到JVM中的呢?别着急,请继续往下看。
打开“java.lang”包下的ClassLoader类。然后将代码翻到loadClass方法:
1 public Class<?> loadClass(String name) throws ClassNotFoundException { 2 return loadClass(name, false); 3 } 4 // -----??----- 5 protected Class<?> loadClass(String name, boolean resolve) 6 throws ClassNotFoundException 7 { 8 // 首先,检查是否已经被类加载器加载过 9 Class<?> c = findLoadedClass(name); 10 if (c == null) { 11 try { 12 // 存在父加载器,递归的交由父加载器 13 if (parent != null) { 14 c = parent.loadClass(name, false); 15 } else { 16 // 直到最上面的Bootstrap类加载器 17 c = findBootstrapClassOrNull(name); 18 } 19 } catch (ClassNotFoundException e) { 20 // ClassNotFoundException thrown if class not found 21 // from the non-null parent class loader 22 } 23 24 if (c == null) { 25 // If still not found, then invoke findClass in order 26 // to find the class. 27 c = findClass(name); 28 } 29 } 30 return c; 31 }
当获取parent是null时,代表获取到的时顶级类加载器,Bootstrap类加载器。
其实这段代码已经很好的解释了双亲委派机制,为了大家更容易理解,我做了一张图来描述一下上面这段代码的流程:
从上图中我们就更容易理解了,当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。那么有人就有下面这种疑问了?
为什么要设计这种机制
这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。
双亲委派机制,其实指的是由子到父,再由父到子的过程。主要解决的是类加载的安全问题(比如java.lang.String,如果没有双亲委派,直接由自定义ClassLoader加载了,有了双亲委派会去父加载器,看是否可以加载)
具体为什么Bootstrap、Extension、App是加载他们自己的类,可以看sun.misc.Lancher,是他们的启动类。相关配置决定了他们所加载的类的路径:
类装载方式
a隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中。
b显式装载, 通过class.forname()等方法,显式加载需要的类
类加载的动态性体现
java应用程序一般是由n个类组成,在程序启动时,并不是一次把所有的类全部加载后再运行,它会先保证程序运行的基础类能够一次性加载到jvm中,等其它类用到JVM的时候再加载,这样就节省了内存的开销,java最早就是为了嵌入式系统而设计的,内存十分珍贵,所以这样节省内存是必然的,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现。
类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示的对象组件。在Java中,类装载器把一个类装入JVM中,要经过以下步骤:
1) 装载:查找和导入Class文件;
2) 链接:把类的二进制数据合并到JRE中;
(a)校验:检查载入Class文件数据的正确性;
(b)准备:给类的静态变量分配存储空间;
(c)解析:将符号引用转成直接引用;
3) 初始化:对类的静态变量,静态代码块执行初始化操作
虚拟机将描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
例:
类java.lang.Object,它存在在rt.jar当中,不管是哪一个类加载器要加载这个类,最后都会是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,所以,Object类在程序的各种类加载器环境中都是同一个类。
相反的来说,假如没有双亲委派模型,而是由各个类加载器自行加载的话,假如,用户编写了一个java.lang.Object的同名类并放在ClassPath中,那么,系统当中将会出现多个不同的Object类,程序将混乱。
所以,假如开发者尝试编写一个与rt.jar类库中重名的Java类,能够正常编译,可是却永远也不能够被加载运行。
JVM懒加载
参考链接:https://blog.csdn.net/codeyanbao/article/details/82875064
https://blog.csdn.net/RaymondCoder/article/details/105941597
https://blog.csdn.net/liuyu973971883/article/details/107582623