天天看点

JVM类加载器和双亲委派模型

在介绍双亲委派模型之前先说下类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。

类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class  对象。

类加载器分类

启动类加载器

BootstrapClassLoader,启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,负责加载存放在 JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被 -Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.开头的类均被 BootstrapClassLoader加载)。启动类加载器是无法被Java程序直接引用的。总结一句话:启动类加载器加载java运行过程中的核心类库JRE\lib\rt.jar, sunrsasign.jar, charsets.jar, jce.jar, jsse.jar, plugin.jar 以及存放在JRE\classes里的类,也就是JDK提供的类等常见的比如:Object、Stirng、List…

扩展类加载器

ExtensionClassLoader,该加载器由 sun.misc.Launcher$ExtClassLoader实现,它负责加载 JDK\jre\lib\ext目录中,或者由 java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器。

应用程序类加载器

ApplicationClassLoader,该类加载器由 sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。总结一句话:应用程序类加载器加载CLASSPATH变量指定路径下的类 即指你自已在项目工程中编写的类.

** **

我们的应用程序都是由这3种类加载器互相配合进行加载的,如果有必要,还可以加入自己定义的类加载器。

双亲委派模型工作过程

如果一个类加载器(ClassLoader)收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需要加载的类)时,子加载器才会尝试自己去加载。当当前类加载器和所有父类加载器都无法加载该类时,抛出ClassNotFindException异常。

使用双亲委托机制的好处是:能够有效确保一个类的全局唯一性,当程序中出现多个限定名相同的类时,类加载器在执行加载时,始终只会加载其中的某一个类。

![](https://img-blog.csdnimg.cn/img_convert/8a2eacc84122abcadb76dc1e567fb4d2.png#align=left&display=inline&height=338&margin=[object Object]&originHeight=356&originWidth=478&status=done&style=none&width=454)

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性.

在类加载器的时候,任何一个JVM启动的时候会分为这几步骤:

1.  启动Bootstrap类加载器 (加载 rt.jar)

2.  启动扩展类加载器 Extension ClassLoader 扩展类加载器 (加载&JAVA_HOME&/lib/ext/*.jar)

在虚拟机启动的过程中,Bootstrap加载器和Extension加载器就已经启动完成了,然后我们自己写的代码是Application 类加载器来加载(加载的是classpath)

为什么需要双亲委派模型呢

提高系统的安全性。用户自定义的类加载器不可能加载应该由父加载器加载的可靠类。(比如用户定义了一个恶意代码,自定义的类加载器首先让系统加载器去加载,系统加载器检查该代码不符合规范,于是就不继续加载了)

假设没有双亲委派模型,试想一个场景:

黑客自定义一个java.lang.String类,该String类具有系统的String类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。并且通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。

而有了双亲委派模型,黑客自定义的java.lang.String类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类。

或许你会想,我在自定义的类加载器里面强制加载自定义的java.lang.String类,不去通过调用父加载器不就好了吗?确实,这样是可行。但是,在JVM中,判断一个对象是否是某个类型时,如果该对象的实际类型与待比较的类型的类加载器不同,那么会返回false。

双亲委派模型

如果你重写一个Object父类,会不会被加载呢?

答案是不会被加载,因为在虚拟机里面默认遵循双亲委派模型.

如果我要去写一个类加载器,它在加载Object的时候,它会去向上面的加载器去询问,询问它的父加载器,这个Object类已经被加载了,这个时候,父加载器也会一级一级的去问它自己的父类,这个Object是否被加载了,最后这个Object一定是由顶层的加载器加载的(Boostrap加载器加载的).

一般来说,我写的自定义加载器都会先问上面的父加载器是不是被加载了,如果上面已经加载了,那么我就不去加载了,如果上面都不加载,那么我写的自定义加载器才会去加载.

自定义加载器的父类是Application加载器,Application加载器的父类是Extension加载器, Extension加载器的父类是Bootstrap加载器.

使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。

命名空间

命名空间概念:

每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。和我们Java中的Package的概念是一样的,和XML中的namespace的概念类似。

特别注意:

在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类。

在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。

由子加载器加载的类能看见父加载器的类,由父亲加载器加载的类不能看见子加载器加载的类