什么是双亲委派??
双亲委派定义
首先了解一个该概念:对于任意一个类,都需要由加载它的类加载器和这个类本身来一同确立其在Java虚拟机中的唯一性。也就是说,一个类在jvm中是唯一的。为了保证唯一,因此用到了双亲委派:当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
双亲委派作用
为什么要用双亲委派模型加载class,原因很简单,保证安全性。举个例子吗,如果你自己定义了一个String,还是在java.lang下的。不走双亲委派的结果是,jvm会用AppClassLoader加载这个String,这样的话本来你先用原生的String结果成了自定义的String,很明显java的语法和基本API的规范和统一是不能保证的。所以java走的双亲委派,并且三个加载器都有对应的加载路径,先从顶级加载器找,没有往下加载,保证了class的唯一性。
不废话,挖源码走起
Lancher
这个类顾名思义,是个启动类,准确的说是JVM启动时候对应的启动类,我们需要实例化和配置的三大类加载器都在里面。看一下源码,lancher实例化时,会把app和ext加载器实例化好并进行设置,由于Bootstrap加载器是c++写的,源码这里没有体现,但是jvm启动时也是会实例化它的
//jvm启动时就会执行这个构造方法
public Launcher() {
// 先创建ext类加载器
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
// 并且将自己的成员变量实例化为app类加载器
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}
// 将app类加载器赋值为线程类加载器,这个就是java的spi用来加载类所用的加载器,spi破坏了双亲委派机制
Thread.currentThread().setContextClassLoader(loader);
// Finally, install a security manager if requested
String s = System.getProperty("java.security.manager");
if (s != null) {
// init FileSystem machinery before SecurityManager installation
sun.nio.fs.DefaultFileSystemProvider.create();
SecurityManager sm = null;
if ("".equals(s) || "default".equals(s)) {
sm = new SecurityManager();
} else {
try {
sm = (SecurityManager)loader.loadClass(s).newInstance();
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
} catch (ClassNotFoundException e) {
} catch (ClassCastException e) {
}
}
if (sm != null) {
System.setSecurityManager(sm);
} else {
throw new InternalError(
"Could not create SecurityManager: " + s);
}
}
}
类加载器
我们常说的三大类加载器其实都在Lancher类下面,是他的静态内部类。而这些内部类都继承一个抽象类ClassLoader。我们现在看一下一个类如何加载。都想毋庸置疑,类会从App类加载器开始加载。
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
int i = name.lastIndexOf('.');
if (i != -1) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPackageAccess(name.substring(0, i));
}
}
if (ucp.knownToNotExist(name)) {
// The class of the given name is not found in the parent
// class loader as well as its local URLClassPath.
// Check if this class has already been defined dynamically;
// if so, return the loaded class; otherwise, skip the parent
// delegation and findClass.
Class<?> c = findLoadedClass(name);
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
throw new ClassNotFoundException(name);
}
return (super.loadClass(name, resolve));
}
看一下这个类加载器的方法内部做了什么。if (ucp.knownToNotExist(name)),ucp是URLClassPath类对象,这个方法是看一下当前类加载器的路径下有没有这个类,找到就返回class对象。那如果没找到会调用super.loadClass(name, resolve),也就是抽象类的方法,咱们也看一下:
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
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
这个loadClass方法就是双亲委派的核心代码,首先会判断一下parent是不是null,这个parent就是classloader对象,app类加载器的parent就是ext加载器。parent不为null就会调用parent的loadClass方法,这就是往上加载。加载到了Bootstrap就会调用findBootstrapClassOrNull方法看看能不能被Bootstrap加载器加载。一轮走下去,还是没加载到class对象,那么就只能让app加载器自己来加载。
弊端??SPI??
那么,双亲委派模型有没有什么问题,其实是有的。这么说吧,我们常常加载jdbc的drive就会打破双亲委派。这是因为,一个类的方法里面加载某个类,首先会从方法所在类对应的类加载器开始加载。加载drive的类处在Bootstrap加载器下,而drive又是我们自己定义的类(第三方包),其实是在app加载器下才能加载的。这时候双亲委派就加载不了drive了,就需要打破双亲委派,这里用到了是Java的SPI机制,可以看看我的另一篇博客。
评论区