三次打破双亲加载机制源码分析

双亲加载机制

以下两张图足够说明(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虚拟机》):

  1. 将以java.*开头的类委派给父类加载器加载
  2. 否则,将委派列表名单内的类,委派给父类加载器加载
  3. 否则,将Import列表中的类,委派给Export这个类的Bundle的类加载器加载
  4. 否则,查找当前Bundle的ClassPath使用主机的类加载器加载
  5. 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载
  6. 否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载
  7. 否则,类查找失败

另补充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

posted @ 2020-11-27 15:54  曹自标  阅读(229)  评论(0编辑  收藏  举报