vlambda博客
学习文章列表

JVM笔记二双亲委派机制

JVM的双亲委派机制

JVM类加载器是什么机制?为什么使用这种机制(这种机制的好处是什么)?说下类加载流程?用代码验证类加载机制。为什么要破坏类的这种加载机制?

我们已经知道了JVM类加载器的四种加载机制,那么这四种加载机制是怎么个加载过程呢?我们再来看看类加载器的图例:

编辑

图一:双亲委派机制图例

先来看看类和类加载器:

类和类加载器

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。当我们比较两个类是否相等的时候,前提是:只有在这两个类是由同一个类加载器加载为前提下才有意义的。

JVM笔记二双亲委派机制

编辑

通过上面文字描述,我们知道了,在JVM中,类可以通过不同类加载器加载的。这个信息很重要。既然类可以通过不同加载器加载后,使其不与其他类equals。那么,是否可以自己写个java.lang.string类呢?我们都知道,自己写的类是appClassLoader加载到JVM的;jvm原生的string类是由bootstrapClassLoader加载的。这两个类是不同的加载器加载,是不eqs的。那么实际上可以吗?答案是:不可以(下文凯哥Java(wxID:kaigejava)会通过代码来证实)。为什么不可以呢?因为双亲委派机制的缘故。

双亲委派机制

如果从JVM角度来讲的话,类的加载器只有两种:启动类加载器。这个类是C++写的,是JVM虚拟机自身的一部分;另一种就是所有其他类的类加载器了。是Java写的, 独立于虚拟机外部的,而且都是继承于:java.lang.ClassLoader的。

从我们Java开发任意角度来看的话,就可以分为四种类加载器了。这里先不具体概述了,在下文会介绍的。

在图一的图例中展示的类加载器之间层次管理,就被称之为双亲委派模型(Parents Delegation Model)。

双亲委派机制药圈,除了顶层的类加载器(Bootstrap)外,其余的类加载器都应该有自己的父类加载器。PS:通过上一篇《JVM学习笔记之类装载器-ClassLoader》的最后,我们通过代码演示了,自定义类的父加载器是appClassLoader,appClassLoader的父加载器是扩展类加载器。

双亲委派机制的执行过程:

如果一个类加载器收到了类加载的请求,这个类加载器不会先尝试加载这个类,而是会先把这个请求委派给自己的父类加载器去完成,在每个层次的类加载器都是依此类推的。因此所有的类加载器请求其最后都应该被委派到顶层的启动类加载器中(Bootstrap),只有当父类的加载器在自己管辖范围内(文末会介绍每个类加载器管理范围的)没有找到所需要的类的时候,子类加载器才会尝试自己去加载的,如果自己管理范围内也找不到需要加载的就会抛出:ClassNotFoundException 这个异常了。这就是为什么如果我们自己写java.lang.string类的时候,是不行的。

我们可以自己尝试着创建一个java.lang.String类,类里面只有一个main方法,执行会是什么样的呢?

编辑

如上图:运行的时候,报错了。根据提示,我们就能知道,自己写的string类在启动的时候,类加载了,向上父类委派,最终委派到启动类加载器了,启动了加载器发现自己管辖的范围内存在String类,然后调用Main方法的时候,发现没有这个方法,就报错了。这个流程执行的简图:

双亲委派流程示意图

编辑

图二:双亲委派机制流程图

双亲委派机制的好处

JVM为什么要使用双亲委派这种机制呢?有什么好处呢?
通过我们自己写的java.lang.string这个类启动报错中,我们就可以得出以下双亲委派的优点:

1:保证了JVM提供的核心类不被篡改,保证class执行安全

比如上文的string类,无论哪个加载器要加载这个类的话,由于双亲委派机制,最终都会交由最顶层的启动类加载器来加载,这样保证了string类在各种类加载器环境中,都是同一个类。试想下,没有双亲委派机制的话,各个加载器自己加载string类,有可能不同类加载器加载的string方法不一样,那样的话,我们的程序是不是就会一片混乱了。

2:防止重复加载同一个class

从图二:双亲委派机制流程图中,我们可以看出,委托向上问一问,如果加载过,就不用再加载了。

双亲委派机制简单理解

简单一句话:我爸是李刚,有事找我爸。简单三个字:往上捅

双亲委派就是,有啥事,先问问老爹,如果老爹不行,再问问爷爷,如果爷爷也没有,再告爸爸,爸爸再告诉诉儿子,你自己看着办吧。

为什么要往上捅呢?是因为沙箱安全机制

四种类加载机制的管辖范围

一:启动类加载器(Bootstrap ClassLoader):

是由c++编写的,是JVM自身的一部分。用来加载Java核心类库的(java.*)的。构造ExtClassLoader和AppClassLoader的。

需要注意的是:由于其是虚拟机自身的一部分,开发者是服务直接获取到启动类加载器的引用的,所以是不允许直接通过引用进行操作。

这个类加载器负责存放在<JAVA_HOME>\lib目录中的,或是被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中

二:扩展类加载器(Extension ClassLoader):

Java语言编写的,加载扩展库。如classPath中的jre,javax.*(也即:<JAVA_HOME>\lib\ext目录中)或是java.ext.dir指定位置中的类。开发者可以直接使用这个扩展类加载器

Java语言编写的,这个加载器是由sun.misc.Launcher$ExtClassLoader来实现的

三:应用程序类加载器(Application ClassLoader)

这个类加载器是由sun.misc.Launcher#AppClassLoader实现的。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值。所以也称为系统加载器。

赋值加载用户类路径(ClassPath)上所指定的类库。

四:用户自定义类加载器(CustomClassLoader)

Java语言编写的,用户自定义类加载器,可以加载指定路径的class文件