# java的双亲委派类加载机制(父亲委派机制可能更准确)

  • 当一个类加载器受到类加载请求是,它首先不会自己去尝试加载类,而是依次将这个请求委托给父类加载器去完成,直到委托到最顶层的启动类加载器为止
  • 只有当父类加载器无法完成该加载项时,子加载器才会尝试加载该类

# 为什么要使用双亲委派机制

  • 可以避免类的重复加载,并且可以确保Java程序的稳定性、安全性、高效性和可靠性;
  • 比如,在应用程序中定义一个java.lang.String类,启动应用程序,最终加载的类,依然是ext.jar中的类
  • 另外,应用程序的类,不能使用java.*的包名,否则不能通过defineClass方法的校验;

# 双亲委派机制的代码解析

# 顶级接口封装

public abstract class ClassLoader {

    //xxx:父类加载器
    private final ClassLoader parent;

    //xxx: 加载类的方法
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        //xxx: 加载类的动作,是串行的(虚拟机内串行)
        synchronized (getClassLoadingLock(name)) {
            //首先检查,该类是否已经被加载过
            Class<?> c = findLoadedClass(name);

            //xxx: 如果未加载过,则进行加载流程
            if (c == null) {

                //xxx: 如果父类不为空,则用父加载器加载, 否则用启动类加载器加载
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }

                //xxx: 如果负加载器,没加载到,则使用当前的类加载器进行加载
                //xxx: 这里实际构成了一个 栈结构, 先进行压栈,所有入栈完毕后,出栈时才进行操作!
                //xxx: 并且,出栈时的操作是有条件的,当前序的出栈没完成目标时,才进行操作
                if (c == null) {
                    c = findClass(name);
                }
                
            }
            
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    //xxx: 当前类加载加载类的方法,由子类实现
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
    
}

# 类加载的层级结构的构造

public class Launcher {

    //xxx: 引导器的类加载器 
    private ClassLoader loader;
    
    //xxx: appClassLoader的类层级结构
    static class AppClassLoader extends URLClassLoader {
        
        //xxx: 类资源的加载路径定义
        final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

        /*xxx: 构建应用类加载器的工厂方法 */
        public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            //xxx: 省略其它抽象...
           return new Launcher.AppClassLoader(var1x, var0);
        }

        //xxx: 内部私有构造器, 父类加载器由框架定义
        AppClassLoader(URL[] var1, ClassLoader var2) {
            super(var1, var2, Launcher.factory);
            this.ucp.initLookupCache(this);
        }

        /*xxx: 加载类资源*/
        public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {

            //xxx: 省略其它抽象...
            
            /*xxx: 该方法用于优化类加载器的性能,如果每次都搜索所有URL,即使不存在,也会花费大量的时间*/
            /*xxx: 通过该方法,避免不必要的搜索, 即不用再压栈*/
            if (this.ucp.knownToNotExist(var1)) {
                Class var5 = this.findLoadedClass(var1);
                if (var5 != null) {
                    if (var2) {
                        this.resolveClass(var5);
                    }

                    return var5;
                } else {
                    throw new ClassNotFoundException(var1);
                }
            } else {
                //xxx: 委托给 双亲机制, 注意,它的直接父类没有覆写该方法
                return super.loadClass(var1, var2);
            }
        }
        
    }

    static class ExtClassLoader extends URLClassLoader {
        
        /*xxx: ext的父类加载器未空, 即 它的父类加载器为 启动类加载器 */
        /*xxx: 且它的父类加载器是不能更改的, 因为是一个 定参, 且没有提供重载方法 */
        public ExtClassLoader(File[] var1) throws IOException {
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }
        
        //xxx:省略抽象, 核心方法均由父类提供...
    }

    public Launcher() {
        //xxx: 省略其它抽象...
        Launcher.ExtClassLoader var1 = Launcher.ExtClassLoader.getExtClassLoader();

        //xxx: 构造双亲结构
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        
        //xxx: 省略其它抽象...
        
        //xxx: 将启动类加载,设置为上下文类加载器 
        Thread.currentThread().setContextClassLoader(this.loader);
    }
}

# 类加载器的实际加载功臣-URLClassLoader

public class URLClassLoader extends SecureClassLoader implements Closeable {
    //xxx: 用于加载资源的路径,内部包含多个资源路径
    private final URLClassPath ucp;

    //xxx: 可以维持类加载器层级关系(但不是必须的), 同时需要指明 用于加载的资源路径
    public URLClassLoader(URL[] urls, ClassLoader parent) {
        super(parent);
        
        //xxx: 省略其它抽象...

        ucp = new URLClassPath(urls, acc);
    }

    public URLClassLoader(URL[] urls) {
        //xxx: 省略其它抽象...
        ucp = new URLClassPath(urls, acc);
    }

    //xxx: 实际查找类的方法
    protected Class<?> findClass(final String name)
            throws ClassNotFoundException
    {
        //xxx: 省略其它抽象...
        
        String path = name.replace('.', '/').concat(".class");
        Resource res = ucp.getResource(path, false);

        if (res != null) {
            try {
                return defineClass(name, res);
            } catch (IOException e) {
                //xxx: 注意,即使存在该类,也可能因为io错误导致类加载异常
                throw new ClassNotFoundException(name, e);
            } catch (ClassFormatError e2) {
                throw e2;
            }
        }else{
            return null;
        }
    }

    //xxx: 获取资源的方法
    public Resource getResource(String var1, boolean var2) {
        int[] var4 = this.getLookupCache(var1);

        URLClassPath.Loader var3;
        for(int var5 = 0; (var3 = this.getNextLoader(var4, var5)) != null; ++var5) {
            Resource var6 = var3.getResource(var1, var2);
            if (var6 != null) {
                return var6;
            }
        }

        return null;
    }

    //xxx: 获取资源的定义
    private static class Loader implements Closeable {
        private final URL base;
        private JarFile jarfile;

        Resource getResource(final String var1, boolean var2) {

            //xxx: 省略其它抽象...
            
            final URL var3 = new URL(this.base, ParseUtil.encodePath(var1, false));

            final URLConnection var4 = var3.openConnection();

            InputStream var8 = var4.getInputStream();
            
            //xxx: 以上的步骤,出现任何异常,都将返回 null

            //xxx: 本质上是流操作
            return new Resource() {
                public String getName() {
                    return var1;
                }

                public URL getURL() {
                    return var3;
                }

                public URL getCodeSourceURL() {
                    return Loader.this.base;
                }

                public InputStream getInputStream() throws IOException {
                    return var8;
                }

                public int getContentLength() throws IOException {
                    return var4.getContentLength();
                }
            };
        }
    }
}

# 双亲委派机制下-加载类的规则(主要是URLClassLoader)

  • 父亲类加载,如果已经加载了类,则子类加载器不会再加载;并且要注意,类加载器本身有保护机制,不得自定义java.*的类
  • 如果不同的jar包,存在同名的类(全限定名相同的类),会使用第一个加载的类并且不会报错;
  • 如果同一个jar包,存在同名的类(全限定名相同的类),则会报类重复定义的错误;(正常的情况下,不会出现这种情况,因为编译期通不过)

# 双亲委派机制的难点

# 理解super.loadClass与parent.loadClass的区别以及二者的联系

  • super.loadClass本身只是交由父类处理,当调用该方法后,即进入了双亲委派机制
  • 双亲委派的实际实现是由paren.loadClass的压栈实现的,类加载器的父子模型,运行时定义
  • 这里实际可以衍生出两个概念,类对象层级实体对象层级,实体对象层级是虚拟的,动态的;类对象层级是确定的,静态的;
  • 双亲委派机制,类对象层级实体对象层级共同完成,也是它的复杂所在;
  • 双亲委派机制,由于包含动态的含义,因此它可能不止两级,可能存在多级;且需要遵循特定的使用规范,比如不能继承于覆写了loadClass的类加载器

# 理解loadClass与findClass的委派关系

  • 类加载器加载类的实际方法,由findClass负责实现
  • 要让类加载器正常运行,必须实现findClass方法,但是它通常有一个特定实现类:URLClassLoader.
  • URLClassLoader是双亲委派模型的关键;

# fatjar原理

  • springBoot可以将多个jar打包成一个jar包,直接执行
  • 需要注意,war本质上也是一个fatjar,不过由于当其在servlet容器中执行时,没有使用fatjar机制;

# springBoot的fatjar类加载器体系源码解析

# fatJar引导器设计

/*xxx: fatjar 的实际启动类*/
public class JarLauncher extends ExecutableArchiveLauncher {

    /*xxx:fatjar 还有一个类似于 classpath的索引信息,存储了项目的所有三方依赖情况  */
    private static final String DEFAULT_CLASSPATH_INDEX_LOCATION = "BOOT-INF/classpath.idx";

    static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
        if (entry.isDirectory()) {
            return entry.getName().equals("BOOT-INF/classes/");
        }
        return entry.getName().startsWith("BOOT-INF/lib/");
    };

    @Override
    /*xxx: BOOT-INF/classes/,BOOT-INF/lib/被判定为包内资源*/
    protected boolean isNestedArchive(Archive.Entry entry) {
        return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
    }

    public static void main(String[] args) throws Exception {
        new JarLauncher().launch(args);
    }
}
/*xxx: war包的实际启动类 */
	/*xxx: 虽然此处有war的执行逻辑, 但实际上 在servlet容器中时, 并不会使用该逻辑*/
	/*xxx: 该逻辑可能是为 以 fatjar的方式启动war而准备的 */
public class WarLauncher extends ExecutableArchiveLauncher {
    @Override
    /*xxx: WEB-INF/classes, WEB-IND/lib/,WEB-INF/lib-provider/被认为是包内资源*/
    public boolean isNestedArchive(Entry entry) {
        if (entry.isDirectory()) {
            return entry.getName().equals("WEB-INF/classes/");
        }
        return entry.getName().startsWith("WEB-INF/lib/") || entry.getName().startsWith("WEB-INF/lib-provided/");
    }

    public static void main(String[] args) throws Exception {
        new WarLauncher().launch(args);
    }
}
public abstract class Launcher {

    /*xxx: 实际的启动流程 */
    protected void launch(String[] args) throws Exception {

        //xxx: 创建本地ClassLoader,为 LaunchedURLClassLoader
        ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator());
        String jarMode = System.getProperty("jarmode");
        String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
        /*xxx: 调用launch引导方法 */
        launch(args, launchClass, classLoader);
    }

    /*xxx: 为特定的 archive 创建 类加载器 */
    /*xxx: 参数为 fatjar 或者war的三方依赖库 或实体类、资源等信息 */
    protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
        List<URL> urls = new ArrayList<>(50);
        while (archives.hasNext()) {
            urls.add(archives.next().getUrl());
        }
        /*xxx: 为这些资源创建类加载器 */
        return createClassLoader(urls.toArray(new URL[0]));
    }

    protected ClassLoader createClassLoader(URL[] urls) throws Exception {
        /*xxx: 创建类加载器, 它的父上下文应该为 AppClassLoader*/
        return new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
    }

    protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
        /*xxx: 将自定义的类加载器,设置为上下文加载器, 即主线程的上下文加载器  */
        Thread.currentThread().setContextClassLoader(classLoader);
        /*xxx: 执行应用层面的启动类 */
        createMainMethodRunner(launchClass, args, classLoader).run();
    }

    protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
        /*xxx: 创建引导启动类 */
        return new MainMethodRunner(mainClass, args);
    }
}

# fatjar类加载器设计

  • fatjar类加载器,主要原因在于其可以加载位于jar包内的jar资源
  • 这应该是fatjar的类加载器最大的区别
/*xxx: fatJar的类加载器 */
public class LaunchedURLClassLoader extends URLClassLoader {

    /*xxx: 创建类加载  它的父上下文应该为 AppClassLoader*/
    public LaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, ClassLoader parent) {
        super(urls, parent);
        this.exploded = exploded;
        /*xxx: 根文档,可能代表 fatjar或者 war*/
        this.rootArchive = rootArchive;
    }

    @Override
    /*xxx: 加载类资源, 遵循父亲委派机制*/
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        //xxx: 省略其它抽象..
        
        return super.loadClass(name, resolve);
    }
    
    
}

# 启动实际的应用

public class MainMethodRunner {
    private final String mainClassName;

    private final String[] args;

    public MainMethodRunner(String mainClass, String[] args) {
        this.mainClassName = mainClass;
        this.args = (args != null) ? args.clone() : null;
    }
    
    public void run() throws Exception {
        /*xxx: 执行应用层面的 启动类, 使用自定义的类加载器加载 启动类,也就是fatjar类加载器-LaunchedURLClassLoader 进行执行   */
        Class<?> mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
        mainMethod.setAccessible(true);
        /*xxx: 通过反射进行执行 */
        mainMethod.invoke(null, new Object[] { this.args });
    }
}

# 常见的servlet容器类加载器机制

# tomcat容器-双亲委派机制的扩展

# 类加载器的结构设计

//xxx: 其会加载 $CATALINA_HOME/lib, $CATALINA_HOME/common, $CATALINA_HOME/shared 目录下的所有JAR文件和类文件
//xxx: 同时还支持对其进行扩展
public class CommonClassLoader extends URLClassLoader {
    
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        // 1. 先从缓存中查找是否已经加载过该类
        Class<?> clazz = findLoadedClass(name);
        if (clazz != null) {
            return clazz;
        }
        // 2. 如果没有加载过,尝试从本地URL中查找类
        try {
            clazz = findClass(name);
            return clazz;
        } catch (ClassNotFoundException ignored) {
        }
        // 3. 如果在本地URL中找不到类,则委派给父类加载
        return super.loadClass(name);
    }
}
public class WebappClassLoader extends WebappClassLoaderBase {

}
public abstract class WebappClassLoaderBase extends URLClassLoader
        implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {

    //xxx: 父亲类加载器
    protected final ClassLoader parent;

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        //加载类时,串行执行
        synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
            Class<?> clazz = null;
            
            //xxx: 首先查找之前是否已经加载过
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                return clazz;
            }

            //xxx: 首先查找之前本地是否已经加载过, 遵循双亲委派机制
            clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
            if (clazz != null) {
                return clazz;
            }

            //xxx: 是否代理委托加载,交给父亲加载器加载,其顺序是 CommonClassLoader->bootstrap->ext->app
            if (delegateLoad) {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    return clazz;
                }
            }

            //xxx: 本地加载
            clazz = findClass(name);
            if (clazz != null) {
                return clazz;
            }

            //xxx: 没有委托的情况下,再让父类加载
            if (!delegateLoad) {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    return clazz;
                }
            }

            throw new ClassNotFoundException(name);
        }
    }

    //xxx: 所有已经加载过的资源均进行缓存
    protected Class<?> findLoadedClass0(String name) {

        String path = binaryNameToPath(name, true);

        ResourceEntry entry = resourceEntries.get(path);
        if (entry != null) {
            return entry.loadedClass;
        }
        return null;
    }
}

# 类加载器的运行设计

/*xxx: 通过这个类,对catalina进行引导启动*/
public final class Bootstrap {

    public static void main(String args[]) {
        synchronized (daemonLock) {
            if (daemon == null) {
                
                //xxx: 省略其它抽象...
                
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.init();

                daemon = bootstrap;
            } else {
                Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
            }
        }
    }

    public void init() throws Exception {
        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);
        
        //xxx: 省略其它抽象...
    }

    private void initClassLoaders() {
        try {
            /*xxx: 通用类加载的的名称为 common,它的父加载器为 null*/
            commonLoader = createClassLoader("common", null);
            if (commonLoader == null) {
                /*xxx: 如果没有指定 common类加载需要加载的路径,则使用 当前类加载,应该是: AppClassLoader*/
                commonLoader = this.getClass().getClassLoader();
            }
            /*xxx: catalina类加载的名称为 server,它的父加载器为 common*/
            catalinaLoader = createClassLoader("server", commonLoader);
            /*xxx: 共享类加载的名称: shared,它的父加载器也为 common*/
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            System.exit(1);
        }
    }

    private ClassLoader createClassLoader(String name, ClassLoader parent)
            throws Exception {
        /*xxx: 没有指定类加载器时,则返回父类加载器*/
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;

        /*xxx: 每个类加载器,都有对应加载的区域,可以自行配置 */
        for (String repository : repositoryPaths) {
            // Check for a JAR URL repository
            try {
                @SuppressWarnings("unused")
                URL url = new URL(repository);
                repositories.add(new Repository(repository, RepositoryType.URL));
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }
            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                        (0, repository.length() - "*.jar".length());
                repositories.add(new Repository(repository, RepositoryType.GLOB));
            } else if (repository.endsWith(".jar")) {
                repositories.add(new Repository(repository, RepositoryType.JAR));
            } else {
                repositories.add(new Repository(repository, RepositoryType.DIR));
            }
        }

        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }
}
public class WebappLoader extends LifecycleMBeanBase
    implements Loader, PropertyChangeListener {

    //xxx: 实际的 WebApplicationClassLoader
    private WebappClassLoaderBase classLoader = null;
    //xxx: 默认采用并行类加载器
    private String loaderClass = ParallelWebappClassLoader.class.getName();
    
    @Override
    //xxx: 启动生命周期组件
    protected void startInternal() throws LifecycleException {
        //xxx: 省略其它抽象...
        
        classLoader = createClassLoader();
        classLoader.setResources(context.getResources());
        classLoader.setDelegate(this.delegate);
        
        //xxx: 省略其它抽象...
        classLoader.start();

    }

    private WebappClassLoaderBase createClassLoader()
            throws Exception {

        //xxx: 如果已经创建过,则不再创建
        if (classLoader != null) {
            return classLoader;
        }

        Class<?> clazz = Class.forName(loaderClass);
        Class<?>[] argTypes = { ClassLoader.class };

        //xxx: 创建类加载器的同时,制定了父类加载器 
        Object[] args = { parentClassLoader };
        
        Constructor<?> constr = clazz.getConstructor(argTypes);
        classLoader = (WebappClassLoaderBase) constr.newInstance(args);
    }
}

# 其它servlet容器,如WebLogic,TongWeb

  • 略,类比即可

# spring-boot热部署类加载器

# springBoot热部署类加载器层级

  1. RestartClassLoader

    • BootClassLoader的子类
    • 当应用程序进行热部署时,它会重新加载已经修改过的类,如果没有被修改过,则委托给BootClassLoader
  2. BootClassLoader

    • 从classpath中加载SpringBoot框架的类库,如spring-boot-starter-web,spring-boot-starter-data-jpa等;
    • 它在热更新的环境时,才存在于三方依赖中
    • 它加载了应用程序的核心类库,整个应用程序运行期间,都不会发生变化,算是提高效率的;
  3. LaunchedURLClassLoader

    • 内容见fatjar部分;
  4. AppClassLoader

    • 内容见父亲委派机制部分;