JVM类加载之双亲委派机制解读
作者:w7h1te
前言
在面试过程中:也会被问到关于如何理解双亲委派模型这样的问题,接下来就通过这篇文章往明白了解一下。
我们首先需要了解一下类加载阶段
类的加载阶段
类加载阶段分为加载、连接、初始化三个阶段,而加载阶段需要通过类的全限定名来获取定义了此类的二进制字节流。Java特意把这一步抽出来用类加载器来实现。把这一步骤抽离出来使得应用程序可以按需自定义类加载器。并且得益于类加载器,OSGI、热部署等领域才得以在JAVA中得到应用。
类加载器除了能用来加载类,还能用来作为类的层次划分。Java自身提供了3种类加载器
类加载器
1、启动类加载器(Bootstrap ClassLoader),它是属于虚拟机自身的一部分,用C++实现的,主要负责加载<JAVA_HOME>\lib目录中或被-Xbootclasspath指定的路径中的并且文件名是被虚拟机识别的文件。它等于是所有类加载器的爸爸。
2、扩展类加载器(Extension ClassLoader),它是Java实现的,独立于虚拟机,主要负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量所指定的路径的类库。
3、应用程序类加载器(Application ClassLoader),它是Java实现的,独立于虚拟机。主要负责加载用户类路径(classPath)上的类库,如果我们没有实现自定义的类加载器那这玩意就是我们程序中的默认加载器。
什么是双亲委派机制
上图:
简单来说:如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。这里的双亲其实就指的是父类,没有mother。父类也不是我们平日所说的那种继承关系,只是调用逻辑是这样。
也就是当类加载器收到类加载的请求时候会首先调用父类(ClassLoader)的findLoadedClass方法,判断类是否加载过,如果已经加载过则直接返回,否则会将加载任务委托给成员变量parent,并不是指的父类。parent加载器在收到类加载请求后,也会先判断需要加载的类是否已经加载过,如果加载过则结束,否则也会将加载任务委托给成员变量parent去进行类加载。这里是一个循环过程,直到将加载任务委托给Bootstrap ClassLoader 结束,如果Bootstrap ClassLoader也没有找到则交给各个子类自己加载,一直到最后,如果没有任何类加载器能加载则会抛出ClassNotFoundException。
为什么要设计双亲委派机制?
- 沙箱安全机制
自己写的java.lang.String.class类不会被加载,这样可以防止核心API库被随意篡改。 为了不让我们写String类,类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。而String类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的String,自己写的String类根本没有机会得到加载。
- 避免类的重复加载
当父亲已经加载了该类, 就没必要子classLoader再加载一次,保证被加载的唯一性。
引申内容
JVM 类加载器和类本身一同确立类在Java虚拟机中的唯一性
问题:由不同类加载器加载同一个类,实例化为对象。使用instanceof判断该对象与该类的归属,请问结果是true还是false?
答案是false。
验证解析
import java.io.IOException; import java.io.InputStream; public class ClassLoaderTest { public static void main(String[] args) throws Exception { ClassLoader myLoader = new ClassLoader() { @SuppressWarnings("ResultOfMethodCallIgnored") @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName); if (is == null) { return super.loadClass(name); } byte[] b = new byte[is.available()]; is.read(b); return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } }; Object obj = myLoader.loadClass("Practice.Java.ClassLoaderTest").newInstance(); System.out.println(obj.getClass()); System.out.println(obj instanceof Practice.Java.ClassLoaderTest); } }
输出结果:
class Practice.Java.ClassLoaderTest
false
原因
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
结果分析
两行输出结果中,从第一句可以看出,这个对象确实是类Practice.Java.ClassLoaderTest实例化出来的对象,但从第二句可以发现,这个对象与类Practice.Java.ClassLoaderTest做所属类型检查的时候却返回了false。
这是因为虚拟机中存在了两个ClassLoaderTest类,一个是由系统应用程序类加载器加载的,另外一个是由我们自定义的类加载器加载的。虽然都来自同一个Class文件,但依然是两个独立的类,做对象所属类型检查时结果自然为false。
结论
Java类加载器这种特性可以简单的总结为命名空间。
即在 Java 虚拟机中,类的唯一性是由类加载器实例以及类的全名一同确定的。即便是同一串字节流,经由不同的类加载器加载,也会得到两个不同的类。
到此这篇关于JVM类加载之双亲委派机制解读的文章就介绍到这了,更多相关JVM双亲委派机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!