zoukankan      html  css  js  c++  java
  • 三次打破双亲加载机制源码分析

    双亲加载机制

    以下两张图足够说明(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

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出。
  • 相关阅读:
    phpmyadmin的登陆配置
    修改xampp-mysql密码
    php实现获取汉字笔画数
    Java学习路线图,专为新手定制的Java学习计划建议
    高德地图Bug 'NSInternalInconsistencyException', reason: 'Invalid parameter not satisfying: !stayUp || CLClientIs
    BitCode
    解决 The sandbox is not sync with the Podfile.lock问题时候,如下所示
    iOS时间戳与字符串
    自定义大头针标注泡泡视图无法响应事件
    'The sandbox is not sync with the Podfile.lock'问题解决
  • 原文地址:https://www.cnblogs.com/caozibiao/p/14048475.html
Copyright © 2011-2022 走看看