是什么?
我们编写的java代码,最终会被编译为.class
文件,再交由JVM去执行。那JVM想要执行程序,就必须要拿到相应的字节码文件。所以这其中,就有了一个类加载的环节。
双亲委托机制就是一种java推荐的加载模型,每个加载器都会有相应的职责划分,它们各施其职,互不越界,从而搭起了字节文件和运行时的桥梁。
怎么做的?
我们编写的每个类可能都会有模块,包名,类名这三个概念,双亲委托机制其实就是根据这些信息来达到相应的加载效果。
这也是为什么我们在不同的模块或者不同的包下编写了相同类名或者相同包名和类名的类,但在加载时并不会产生冲突的原因。
内部细节?
具体需要关注四种类加载器:
1、Bootstrap;
2、ExtClassLoader;
3、AppClassLoader;
4、CustomClassLoader;
职责划分
Bootstrap
:加载<JAVA_HOME>/lib/
下的文件或者从-Dsun.boot.class.path
指定的文件夹中加载类库。该加载器出于安全角度会根据文件名识别jar包,只加载sun,java,javax
等开头的类。实现依赖于底层操作系统,是虚拟机实现的一部分。没有继承java.lang.ClassLoader
。
ExtClassLoader
:Launcher的静态内部类。从-Djava.ext.dirs
系统属性指定的目录中加载类库,或者从JDK的安装目录下的<JAVA_HOME>/jre/lib/ext/
中加载。如果将创建后的jar文件放到该指定目录下,则对应jar下面的类也由该类加载器进行加载。此类加载器由纯java类,继承了java.lang.ClassLoader
,只根据jar文件加载。
AppClassLoader
:也称系统类加载器。加载从java -classpath
或者-Djava.class.path
的文件。一般在程序中默认的就是该类加载器,你也可以理解为我们的工程项目加载使用的类加载器,调用ClassLoader.getSystemClassLoader()
就能直接获取到该类加载器。
CustomClassLoader
:这个是用户自定义类加载器的统称,在日常开发中可以根据需要去自定义自己的类加载器。
在双亲委托模型中,除了Bootstrap,其他的类加载器都有一个对应的父类。即ExtClassLoader的父类是Bootstrap,AppClassLoader的父类是ExtClassLoader,CustomClassLoader的父类是AppClassLoader。
工作流程
如何打破该机制
1、利用线程本地类加载器。在运行时调用Thread.currentThread().setContextClassLoader(ClassLoader loader);
将目标类加载器设置进去,然后在finally中将原来的ClassLoader还原
。
2、重写ClassLoader的loadClass()
方法。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 这里调用了父类进行加载类
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
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;
}
}
有啥好处?
1、防止重复加载类对象。所以判断两个对象是否属于同个类,不仅要判断class是否相同,还要判断classLoader是否相同。
2、类似java.lang.Object都是由Bootstrap进行加载的,这样能够保证其生成的对象都是来自于同一个类,从而保证项目使用类库的兼容。所以,当面试官吃饱了撑着让你给它搞个java.lang.Object的时候,你可以通过自定义类加载器的方式。除此之外,当你想要跟踪某个类的来源的时候,也可以通过类加载器去进行辨别。