三次打破双亲加载机制源码分析
双亲加载机制
以下两张图足够说明(jdk1.8)双亲加载机制。
注意的是AppClassLoader和ExtClassLoader都是sun.misc.Launcher的内部类,同样都继承URLClassLoader,parent是ClassLoader中字段,当初始化AppClassLoader时,它的parent就被设置ExtClassLoader,而ExtClassLoader的parent则被设置为null,用户自定义的则被设置AppClassLoader。
private final ClassLoader parent;
@CallerSensitive
public final ClassLoader getParent() {
if (parent == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// Check access to the parent class loader
// If the caller's class loader is same as this class loader,
// permission check is performed.
checkClassLoaderPermission(parent, Reflection.getCallerClass());
}
return parent;
}
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;
}
}
第一次打破双亲加载机制
为何出现的原由?
双亲加载模型是在JDK1.2之后才被引入,自然为向前兼容,没有把loadClass方法设置为不可覆盖的方法,而是新增加了findClass方法,让用户尽量重写此方法。
换个方向理解,其实就是可以覆盖loadClass方法,进行直接加载一些类。如下面例子,自定义加载器继承ClassLoader,在loadClass方法中,删除双亲加载逻辑,后面增加一个判断,如果不是自己的包下文件,让父类去加载。而测试类,直接使用自定义加载器去loadClass时,返回的类的加载器是此自定义加载器。
一个例子:
public class FirstClassLoader extends ClassLoader{
private String classPath;
public FirstClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] getByte(String name) throws Exception{
name = name.replaceAll("\\.", "/");
FileInputStream fileInputStream = new FileInputStream(classPath + "/" + name + ".class");
int len = fileInputStream.available();
byte [] data = new byte[len];
fileInputStream.read(data);
fileInputStream.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try{
byte [] data = getByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
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();
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 除了删除上面双亲加载机制地方,要加这个判断,不然会Object等一些类无法加载
if(!name.startsWith("com.java.study")) {
return this.getParent().loadClass(name);
}
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;
}
}
}
测试类:
public class ClassLoaderTest {
public static void main(String[] args) throws Exception{
// 传入你要load的class路径
FirstClassLoader firstClassLoader = new FirstClassLoader("xxxx");
Class clazz = firstClassLoader.loadClass("com.java.study.StudyApplication");
System.out.println(clazz.getClassLoader());
}
}
第二次打破双亲加载机制
第二次打破双亲加载机制,出现在JNDI服务中。了解前可以先熟悉SPI(Service Provider Interface)。(参考:https://www.zhihu.com/question/49667892)
对应JDBC例子就是,调用方是rt.jar包下DriverManager,而具体Driver实现方在三方jar包下(如mysql-connector-java.jar),Bootstrap Class Loader加载的类在加载过程中要使用Application Class Loader才能加载的类,在双亲加载模型是不能做到的。所以这里出现打破双亲加载机制。具体分析如下:
先看获取连接写法:
Connection connection = DriverManager.getConnection("jdbc:mysql://192.168.130.1:3306/test", "root", "password");
再分析DriverManager类,其中静态代码块在类加载过程中进行初始化调用loadInitialDrivers(),接下来调用ServiceLoader#load(), 此方法中引入Thread.currentThread().getContextClassLoader(),即Application Class Loader,是具体打破双亲加载机制的实现。后面加载Driver具体实现类,便可用此Loader
public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
......
}
}
}
}
public final class ServiceLoader<S> implements Iterable<S> {
public static <S> ServiceLoader<S> load(Class<S> service) {
// 打破双亲加载机制具体实现
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
}
在Thread类中可以看到,初始化时会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,这个类加载器默认就是Application Class Loader。
public class Thread implements Runnable {
private ClassLoader contextClassLoader;
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
Thread parent = currentThread();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
}
public void setContextClassLoader(ClassLoader cl) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setContextClassLoader"));
}
contextClassLoader = cl;
}
public ClassLoader getContextClassLoader() {
if (contextClassLoader == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(contextClassLoader,
Reflection.getCallerClass());
}
return contextClassLoader;
}
}
解下来重要方法一,ServiceLoader#hasNextService() 中会去扫描jar包下META-INF/services/java.sql.Driver文件,再通过parse(service, configs.nextElement())方法去获取Driver具体实现类com.mysql.cj.jdbc.Driver等。
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 加载jar包下META-INF/services/java.sql.Driver文件
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 获取具体实现类
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
重要方法二,ServiceLoader#nextService()先传入全路径名和Loader获取com.mysql.cj.jdbc.Driver未初始化的实例对象,再在c.newInstance()中进行初始化
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 获取com.mysql.cj.jdbc.Driver
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
其中com.mysql.cj.jdbc.Driver源码中可看到,类加载时会往DriverManager中注册Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
// 往DriverManager中注册Driver
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
最后到具体DriverManager#getConnection()方法。由于在加载com.mysql.cj.jdbc.Driver时已经设置classLoader为Application class Loader,此时callerCL不为null。直接走下面遍历注册的Drivers,获取连接
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
Connection con = aDriver.driver.connect(url, info);
} catch (SQLException ex) {
......
}
}
}
}
以上第二次打破双亲加载机制就全部分析完,主要实现便是引入Thread.currentThread().getContextClassLoader()。
第三次打破双亲加载机制
第三次打破双亲加载机制由于用户对程序动态性追求而导致的,如热部署。主要可研究OSGi原理。
先把OSGi类搜索顺序摘录(《深入理解java虚拟机》):
- 将以java.*开头的类委派给父类加载器加载
- 否则,将委派列表名单内的类,委派给父类加载器加载
- 否则,将Import列表中的类,委派给Export这个类的Bundle的类加载器加载
- 否则,查找当前Bundle的ClassPath使用主机的类加载器加载
- 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载
- 否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载
- 否则,类查找失败
另补充JDK9后对应源码,ClassLoader中一些改变。
新增BuiltinClassLoader;ExtClassLoader替换为PlatformClassLoader。新增BootClassLoader。AppClassLoader、PlatformClassLoader、BootClassLoader三者都继承BuiltinClassLoader。如下图:
在AppClassLoader中先委派父类加载器进行加载,查询要加载的包是否在module中:
不在,委派parent去加载;
在,并和当前ClassLoader相同,使用PlatformClassLoader去module中加载;
在,但和当前ClassLoader不同,使用其他加载器加载
private static class AppClassLoader extends BuiltinClassLoader {
@Override
protected Class<?> loadClass(String cn, boolean resolve) throws ClassNotFoundException{
return super.loadClass(cn, resolve);
}
}
public class BuiltinClassLoader extends SecureClassLoader {
@Override
protected Class<?> loadClass(String cn, boolean resolve) throws ClassNotFoundException
{
Class<?> c = loadClassOrNull(cn, resolve);
if (c == null)
throw new ClassNotFoundException(cn);
return c;
}
}
public class BuiltinClassLoader extends SecureClassLoader {
protected Class<?> loadClassOrNull(String cn, boolean resolve) {
synchronized (getClassLoadingLock(cn)) {
// check if already loaded
Class<?> c = findLoadedClass(cn);
if (c == null) {
// find the candidate module for this class
LoadedModule loadedModule = findLoadedModule(cn);
if (loadedModule != null) {
// package is in a module
BuiltinClassLoader loader = loadedModule.loader();
if (loader == this) {
if (VM.isModuleSystemInited()) {
c = findClassInModuleOrNull(loadedModule, cn);
}
} else {
// delegate to the other loader
c = loader.loadClassOrNull(cn);
}
} else {
// check parent
if (parent != null) {
c = parent.loadClassOrNull(cn);
}
// check class path
if (c == null && hasClassPath() && VM.isModuleSystemInited()) {
c = findClassOnClassPathOrNull(cn);
}
}
}
if (resolve && c != null)
resolveClass(c);
return c;
}
}
}
参考:
《深入理解java虚拟机》
https://www.bilibili.com/video/BV1RK4y1a7NF?p=6
https://www.jianshu.com/p/09f73af48a98
https://www.cnblogs.com/jay-wu/p/11590571.html
https://www.jianshu.com/p/78f5e2103048
https://www.cnblogs.com/lyc88/articles/11431383.html
https://www.cnblogs.com/huxuhong/p/11856786.html
https://www.zhihu.com/question/49667892
https://www.jianshu.com/p/5dc10732de6a
https://www.jianshu.com/p/a18aecaecc89