学习笔记----Classloader源码解析

309 阅读18分钟

1. 定义

来自维基百科的词条:

Java类加载器(英语:Java Classloader)是Java运行时环境(Java Runtime Environment)的一个部件,负责动态加载Java类Java虚拟机的内存空间中。[1]类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。对学习类加载器而言,掌握Java的委派概念是很重要的。

每个Java类必须由某个类加载器装入到内存。[2]Java程序可以通过类加载器来利用外部库(即由其他作者编写的软件库)。

JVM中有3个默认的类加载器:[3][4]

  1. 引导(Bootstrap)类加载器。由原生代码(如C语言)编写,不继承自java.lang.ClassLoader。负责加载核心Java库[5],存储在<JAVA_HOME>/jre/lib目录中。
  2. 扩展(Extensions)类加载器。用来在<JAVA_HOME>/jre/lib/ext,[6]java.ext.dirsdocs.oracle.com/javase/6/do… Java的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。该类由sun.misc.Launcher$ExtClassLoader实现。
  3. Apps类加载器(也称系统类加载器)。根据 Java应用程序的类路径(java.class.path或CLASSPATH环境变量)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。该类由sun.misc.Launcher$AppClassLoader实现。

每个类装载器通过组合的方式包含一个父装载器(parent class loader)。

JDK 1.2之后引入“双亲委派”方式来实现类加载器的层次调用,以尽可能保证JDK的系统API不会被用户定义的类加载器所破坏,但一些使用场景会打破这个惯例来实现必要的功能。

zh.wikipedia.org/wiki/Java%E…

en.wikipedia.org/wiki/Java_C…

注1:提出一些个人见解,上面的JVM中有3个默认的类加载器这句的一些疑问,

在《Java虚拟机规范(Java SE 8)》中的5.3节中有这么一句话:

There are two kinds of class loaders: the bootstrap class loader supplied by the Java

Virtual Machine, and user-defined class loaders. Every user-defined class loader is

an instance of a subclass of the abstract class ClassLoader. Applications employ

user-defined class loaders in order to extend the manner in which the Java Virtual

Machine dynamically loads and thereby creates classes. User-defined class loaders

can be used to create classes that originate from user-defined sources. For example,

a class could be downloaded across a network, generated on the fly, or extracted

from an encrypted file.

翻译过来就是:

Java 虚拟机支持两种类加载器:Java 虚拟机提供的引导类加载器(Bootstrap Class

Loader)和用户自定义类加载器(User-Defined Class Loader)。每个用户自定义的类加

载器应该是抽象类 ClassLoader 的某个子类的实例。应用程序使用用户自定义类加载器是为了

便于扩展 Java 虚拟机的功能,支持动态加载并创建类。当然,它也可以从用户自定义的数据来源

来获取类的二进制表示并创建类。例如,用户自定义类加载器可以通过网络下载、动态产生或是从

一个加密文件中提取类的信息。

所以是维基百科说错了??????

其实是从不同的角度来理解的,在《深入理解Java虚拟机》第三版,这本书中已经给了答案,在7.4章节,

1.站在Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是其他所有的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

2.站在Java开发人员的角度来看,类加载器就应当划分得更细致一些。自JDK 1.2以来,Java一直保持着三层类加载器、双亲委派的类加载架构,尽管这套架构在Java模块化系统出现后有了一些调整变动,但依然未改变其主体结构。

也就是说,因为Extensions class loaderSystem class loader这两个类加载器是用Java语言实现的,都是java.lang.ClassLoader的子类,是Java层面的代码实现。所以对于JVM一部分的Bootstrap class loader这个用C++实现的类加载器来说,这俩用Java实现的就都算是外部的、自定义的类加载器了。所以是从不同的角度来理解的。

**注2:**在维基百科的词条“JVM”中en.wikipedia.org/wiki/Java_v…

说了有很多的Java 虚拟机实现,One of Oracle's JVMs is named HotSpot; the other, inherited from [BEA Systems](https://en.wikipedia.org/wiki/BEA_Systems), is [JRockit](https://en.wikipedia.org/wiki/JRockit).

而上面的词条Java类加载器直接就说有三种类加载的实现,并没有说是具体哪种虚拟机的,而且看上去说的都是我们熟悉的HotSpot。维基百科这么的不严谨么?

其实仔细想想,注1中就说了,对于Java 虚拟机来说,只有两种,一种是Java 虚拟机自己提供的,另一种是JAVA语言层面的实现了ClassLoader类的自定义类加载器。那么既然是JAVA语言,最终都会编译成字节码,字节码跑在Java 虚拟机上。众所周知,JAVA最初是SUN公司写的,所以像类加载器这种最基础JAVA代码早就是写好的。所有的JVM实现者们,只要实现作为JVM一部分的那个Bootstrap Class Loader就可以了,对于自定义的类加载器,直接拿SUN公司实现好了的就行了。

为了验证猜想,我下载了JRockit的JDK,发现,它就是使用的和我们用的Oracle的JDK中一样的java类,sun.misc.Launcher,这里面有sun.misc.Launcher.AppClassLoadersun.misc.Launcher.ExtClassLoader这两个JAVA语言层面的自定义类加载器。

下面从Java开发人员的角度根据JDK8来进行介绍。

2. 两种不同模型

在介绍什么是双亲委派之前,先来了解一下这三个内置的类加载。

  1. 启动类加载器(Bootstrap class loader)

这个类加载器负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机的内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可,下面代码的展示的就是java.lang.Class#getClassLoader()方法的代码片段,其中的注释和代码实现都明确地说明了以null值来代表引导类加载器的约定规则。

 /**
     * Returns the class loader for the class.  Some implementations may use
     * null to represent the bootstrap class loader. This method will return
     * null in such implementations if this class was loaded by the bootstrap
     * class loader.
     */
public ClassLoader getClassLoader() {
    ClassLoader cl = getClassLoader0();
    if (cl == null)
        return null;
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
    }
    return cl;
}
  1. 扩展类加载器(Extensions class loader)

这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。根据“扩展类加载器”这个名称,就可以推断出这是一种Java系统类库的扩展机制,JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能,在JDK 9之后,这种扩展机制被模块化带来的天然的扩展能力所取代。由于扩展类加载器是由Java代码实现的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。

  1. 应用程序类加载器\系统类加载器(Application class loader\System class loader)

这个类加载器由sun.misc.Launcher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的java.lang.ClassLoader#getSystemClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径(ClassPath,java.class.pathCLASSPATH环境变量)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

官网的相关链接:

java.ext.dirs是啥?

docs.oracle.com/javase/8/do…

java.class.path是啥?以及官网对于三种类的加载描述(正好对应了维基百科描述的三种类加载器)

docs.oracle.com/javase/8/do…

JAVA_HOME是啥?

docs.oracle.com/javase/8/do…

官网对于ClassLoader的描述。以及双亲委派的描述。

docs.oracle.com/javase/tuto…

2.1JVM启动的时候,和类加载器相关的步骤

所以我们先来看看sun.misc.Launcher(这个类在rt.jar这个包中,所以在初始化BootStrapClassLoader的时候会被加载,具体怎么初始化和加载的看这篇文章huangzhilin.blog.csdn.net/article/det…)

sun.misc.Launcher这个类的代码如下:


package sun.misc;
//该类由Bootstrap Class Loader这个类加载器加载,在类加载的时候,会初始,new一个Launcher,看它的构造方法
public class Launcher {
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
    //静态变量,Launcher对象本身
    private static Launcher launcher = new Launcher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    //AppClassLoader这个类加载器的实例
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;

    public static Launcher getLauncher() {
        return launcher;
    }

    public Launcher() {
        // Create the extension class loader
        //创建ExtClassLoader实例
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            //获取AppClassLoader实例,入参是上面创建的ExtClassLoader实例
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }
    //返回AppClassLoader这个类加载器的实例
    public ClassLoader getClassLoader() {
        return this.loader;
    }

    public static URLClassPath getBootstrapClassPath() {
        return Launcher.BootClassPathHolder.bcp;
    }

    private static URL[] pathToURLs(File[] var0) {
        URL[] var1 = new URL[var0.length];

        for(int var2 = 0; var2 < var0.length; ++var2) {
            var1[var2] = getFileURL(var0[var2]);
        }

        return var1;
    }

    private static File[] getClassPath(String var0) {
        File[] var1;
        if (var0 != null) {
            int var2 = 0;
            int var3 = 1;
            boolean var4 = false;

            int var5;
            int var7;
            for(var5 = 0; (var7 = var0.indexOf(File.pathSeparator, var5)) != -1; var5 = var7 + 1) {
                ++var3;
            }

            var1 = new File[var3];
            var4 = false;

            for(var5 = 0; (var7 = var0.indexOf(File.pathSeparator, var5)) != -1; var5 = var7 + 1) {
                if (var7 - var5 > 0) {
                    var1[var2++] = new File(var0.substring(var5, var7));
                } else {
                    var1[var2++] = new File(".");
                }
            }

            if (var5 < var0.length()) {
                var1[var2++] = new File(var0.substring(var5));
            } else {
                var1[var2++] = new File(".");
            }

            if (var2 != var3) {
                File[] var6 = new File[var2];
                System.arraycopy(var1, 0, var6, 0, var2);
                var1 = var6;
            }
        } else {
            var1 = new File[0];
        }

        return var1;
    }

    static URL getFileURL(File var0) {
        try {
            var0 = var0.getCanonicalFile();
        } catch (IOException var3) {
        }

        try {
            return ParseUtil.fileToEncodedURL(var0);
        } catch (MalformedURLException var2) {
            throw new InternalError(var2);
        }
    }

    static class AppClassLoader extends URLClassLoader {
        final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

        public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
            //var0是ExtClassLoader实例
            
            //获取环境变量"java.class.path",也就是我们的java -cp命令的参数
            //在我的电脑这个值是,算了太多了,不贴了
            final String var1 = System.getProperty("java.class.path");
            //获取路径中,class文件对应的File对象
            final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
            return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
                public Launcher.AppClassLoader run() {
                    URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                    //调用AppClassLoader的构造函数,//var0是ExtClassLoader实例
                    return new Launcher.AppClassLoader(var1x, var0);
                }
            });
        }

        AppClassLoader(URL[] var1, ClassLoader var2) {
            //调用父类URLClassLoader的构造函数
            super(var1, var2, Launcher.factory);
            this.ucp.initLookupCache(this);
        }

        public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
            int var3 = var1.lastIndexOf(46);
            if (var3 != -1) {
                SecurityManager var4 = System.getSecurityManager();
                if (var4 != null) {
                    var4.checkPackageAccess(var1.substring(0, var3));
                }
            }

            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 {
                return super.loadClass(var1, var2);
            }
        }

        protected PermissionCollection getPermissions(CodeSource var1) {
            PermissionCollection var2 = super.getPermissions(var1);
            var2.add(new RuntimePermission("exitVM"));
            return var2;
        }

        private void appendToClassPathForInstrumentation(String var1) {
            assert Thread.holdsLock(this);

            super.addURL(Launcher.getFileURL(new File(var1)));
        }

        private static AccessControlContext getContext(File[] var0) throws MalformedURLException {
            PathPermissions var1 = new PathPermissions(var0);
            ProtectionDomain var2 = new ProtectionDomain(new CodeSource(var1.getCodeBase(), (Certificate[])null), var1);
            AccessControlContext var3 = new AccessControlContext(new ProtectionDomain[]{var2});
            return var3;
        }

        static {
            ClassLoader.registerAsParallelCapable();
        }
    }

    private static class BootClassPathHolder {
        static final URLClassPath bcp;

        private BootClassPathHolder() {
        }

        static {
            URL[] var0;
            if (Launcher.bootClassPath != null) {
                var0 = (URL[])AccessController.doPrivileged(new PrivilegedAction<URL[]>() {
                    public URL[] run() {
                        File[] var1 = Launcher.getClassPath(Launcher.bootClassPath);
                        int var2 = var1.length;
                        HashSet var3 = new HashSet();

                        for(int var4 = 0; var4 < var2; ++var4) {
                            File var5 = var1[var4];
                            if (!var5.isDirectory()) {
                                var5 = var5.getParentFile();
                            }

                            if (var5 != null && var3.add(var5)) {
                                MetaIndex.registerDirectory(var5);
                            }
                        }

                        return Launcher.pathToURLs(var1);
                    }
                });
            } else {
                var0 = new URL[0];
            }

            bcp = new URLClassPath(var0, Launcher.factory, (AccessControlContext)null);
            bcp.initLookupCache((ClassLoader)null);
        }
    }

    static class ExtClassLoader extends URLClassLoader {
        //静态变量,单例模式
        private static volatile Launcher.ExtClassLoader instance;

        public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
            if (instance == null) {
                Class var0 = Launcher.ExtClassLoader.class;
                synchronized(Launcher.ExtClassLoader.class) {
                    if (instance == null) {
                        //创建一个ExtClassLoader实例
                        instance = createExtClassLoader();
                    }
                }
            }

            return instance;
        }

        private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
            try {
                return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
                    public Launcher.ExtClassLoader run() throws IOException {
                        //获取ExtClassLoader要加载的类文件
                        File[] var1 = Launcher.ExtClassLoader.getExtDirs();
                        int var2 = var1.length;

                        for(int var3 = 0; var3 < var2; ++var3) {
                            MetaIndex.registerDirectory(var1[var3]);
                        }
                        //调用ExtClassLoader的构造方法,入参是class文件数组
                        return new Launcher.ExtClassLoader(var1);
                    }
                });
            } catch (PrivilegedActionException var1) {
                throw (IOException)var1.getException();
            }
        }

        void addExtURL(URL var1) {
            super.addURL(var1);
        }

        public ExtClassLoader(File[] var1) throws IOException {
            //看它的父类URLClassLoader的构造方法
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
            SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
        }

        private static File[] getExtDirs() {
            //获取系统变量"java.ext.dirs",即,这个类加载要加载的class文件的路径
            //我的本地是"D:\Program Files\Java\jdk1.8.0_221\jre\lib\ext;C:\Windows\Sun\Java\lib\ext"
            String var0 = System.getProperty("java.ext.dirs");
            File[] var1;
            if (var0 != null) {
                StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
                int var3 = var2.countTokens();
                var1 = new File[var3];

                for(int var4 = 0; var4 < var3; ++var4) {
                    //创建类文件对应的File
                    var1[var4] = new File(var2.nextToken());
                }
            } else {
                var1 = new File[0];
            }

            return var1;
        }

        private static URL[] getExtURLs(File[] var0) throws IOException {
            Vector var1 = new Vector();

            for(int var2 = 0; var2 < var0.length; ++var2) {
                String[] var3 = var0[var2].list();
                if (var3 != null) {
                    for(int var4 = 0; var4 < var3.length; ++var4) {
                        if (!var3[var4].equals("meta-index")) {
                            File var5 = new File(var0[var2], var3[var4]);
                            var1.add(Launcher.getFileURL(var5));
                        }
                    }
                }
            }

            URL[] var6 = new URL[var1.size()];
            var1.copyInto(var6);
            return var6;
        }

        public String findLibrary(String var1) {
            var1 = System.mapLibraryName(var1);
            URL[] var2 = super.getURLs();
            File var3 = null;

            for(int var4 = 0; var4 < var2.length; ++var4) {
                URI var5;
                try {
                    var5 = var2[var4].toURI();
                } catch (URISyntaxException var9) {
                    continue;
                }

                File var6 = Paths.get(var5).toFile().getParentFile();
                if (var6 != null && !var6.equals(var3)) {
                    String var7 = VM.getSavedProperty("os.arch");
                    File var8;
                    if (var7 != null) {
                        var8 = new File(new File(var6, var7), var1);
                        if (var8.exists()) {
                            return var8.getAbsolutePath();
                        }
                    }

                    var8 = new File(var6, var1);
                    if (var8.exists()) {
                        return var8.getAbsolutePath();
                    }
                }

                var3 = var6;
            }

            return null;
        }

        private static AccessControlContext getContext(File[] var0) throws IOException {
            PathPermissions var1 = new PathPermissions(var0);
            ProtectionDomain var2 = new ProtectionDomain(new CodeSource(var1.getCodeBase(), (Certificate[])null), var1);
            AccessControlContext var3 = new AccessControlContext(new ProtectionDomain[]{var2});
            return var3;
        }

        static {
            ClassLoader.registerAsParallelCapable();
            instance = null;
        }
    }

    private static class Factory implements URLStreamHandlerFactory {
        private static String PREFIX = "sun.net.www.protocol";

        private Factory() {
        }

        public URLStreamHandler createURLStreamHandler(String var1) {
            String var2 = PREFIX + "." + var1 + ".Handler";

            try {
                Class var3 = Class.forName(var2);
                return (URLStreamHandler)var3.newInstance();
            } catch (ReflectiveOperationException var4) {
                throw new InternalError("could not load " + var1 + "system protocol handler", var4);
            }
        }
    }
}

java.net.URLClassLoader#URLClassLoader(java.net.URL[], java.lang.ClassLoader, java.net.URLStreamHandlerFactory)

public URLClassLoader(URL[] urls, ClassLoader parent,
                      URLStreamHandlerFactory factory) {
    //看它的父类java.security.SecureClassLoader#SecureClassLoader(java.lang.ClassLoader)方法
    //对于ExtClassLoader,这个parent是null值
    //对于AppClassLoader,这个parent是ExtClassLoader
    super(parent);
    // this is to make the stack depth consistent with 1.1
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkCreateClassLoader();
    }
    acc = AccessController.getContext();
    ucp = new URLClassPath(urls, factory, acc);
}

java.security.SecureClassLoader#SecureClassLoader(java.lang.ClassLoader)

protected SecureClassLoader(ClassLoader parent) {
    //看它的父类java.lang.ClassLoader#ClassLoader(java.lang.ClassLoader)方法
    //对于ExtClassLoader,这个parent是null值
    //对于AppClassLoader,这个parent是ExtClassLoader
    super(parent);
    // this is to make the stack depth consistent with 1.1
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkCreateClassLoader();
    }
    initialized = true;
}

java.lang.ClassLoader#ClassLoader(java.lang.ClassLoader)

protected ClassLoader(ClassLoader parent) {
    //看它的重载方法java.lang.ClassLoader#ClassLoader(java.lang.Void, java.lang.ClassLoader)
    //对于ExtClassLoader,这个parent是null值
    //对于AppClassLoader,这个parent是ExtClassLoader
    this(checkCreateClassLoader(), parent);
}

java.lang.ClassLoader#ClassLoader(java.lang.Void, java.lang.ClassLoader)

private ClassLoader(Void unused, ClassLoader parent) {
    //设置当前的类加载器的父加载器
    //对于ExtClassLoader,这个parent是null值
    //对于AppClassLoader,这个parent是ExtClassLoader
        this.parent = parent;
    if (ParallelLoaders.isRegistered(this.getClass())) {
        parallelLockMap = new ConcurrentHashMap<>();
        package2certs = new ConcurrentHashMap<>();
        domains =
            Collections.synchronizedSet(new HashSet<ProtectionDomain>());
        assertionLock = new Object();
    } else {
        // no finer-grained lock; lock on the classloader instance
        parallelLockMap = null;
        package2certs = new Hashtable<>();
        domains = new HashSet<>();
        assertionLock = this;
    }
}

**已经准备好了所有的类加载器了,虚拟机是怎么加载主类的?**www.ireader.com.cn/index.php?c…

接上面的图中的LoadMainClass()继续走,

JVM源码中会直接获取sun.launcher.LauncherHelper这个java类,然后调用它的sun.launcher.LauncherHelper#checkAndLoadMain方法

public enum LauncherHelper {
    //.....................
    //其实就是AppClassLoader对象的实例,ClassLoader.getSystemClassLoader()其实就是从上面的sun.misc.Launcher类中获取的
    private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
    //.....................
 public static Class<?> checkAndLoadMain(boolean var0, int var1, String var2) {
    initOutput(var0);
    String var3 = null;
    switch(var1) {
    case 1:
        var3 = var2;
        break;
    case 2:
        var3 = getMainClassFromJar(var2);
        break;
    default:
        throw new InternalError("" + var1 + ": Unknown launch mode");
    }

    var3 = var3.replace('/', '.');
    Class var4 = null;

    try {
        //这里,调用scloader的loadClass方法,scloader是一个ClassLoader对象,其实就是AppClassLoader对象,下面就是开始加载类的逻辑了
        //看sun.misc.Launcher.AppClassLoader#loadClass的方法,就进入了双亲委派的逻辑了
        var4 = scloader.loadClass(var3);
    } catch (ClassNotFoundException | NoClassDefFoundError var8) {
        if (System.getProperty("os.name", "").contains("OS X") && Normalizer.isNormalized(var3, Form.NFD)) {
            try {
                var4 = scloader.loadClass(Normalizer.normalize(var3, Form.NFC));
            } catch (ClassNotFoundException | NoClassDefFoundError var7) {
                abort(var8, "java.launcher.cls.error1", var3);
            }
        } else {
            abort(var8, "java.launcher.cls.error1", var3);
        }
    }

    appClass = var4;
    if (!var4.equals(LauncherHelper.FXHelper.class) && !LauncherHelper.FXHelper.doesExtendFXApplication(var4)) {
        validateMainClass(var4);
        return var4;
    } else {
        LauncherHelper.FXHelper.setFXLaunchParameters(var2, var1);
        return LauncherHelper.FXHelper.class;
    }
}

2.2双亲委派型

JDK 9之前的Java应用都是由这三种类加载器互相配合来完成加载的,如果用户认为有必要,还可以加入自定义的类加载器来进行拓展,典型的如增加除了磁盘位置之外的Class文件来源,或者通过类加载器实现类的隔离、重载等功能。这些类加载器之间的协作关系“通常”会如上图所示。

上图中展示的各种类加载器之间的层次关系被称为类加载器的“双亲委派模型(Parents Delegation Model)”。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合(Composition)关系来复用父加载器的代码。

读者可能注意到前面描述这种类加载器协作关系时,笔者专门用双引号强调这是“通常”的协作关系。类加载器的双亲委派模型在JDK 1.2时期被引入,并被广泛应用于此后几乎所有的Java程序中,但它并不是一个具有强制性约束力的模型,而是Java设计者们推荐给开发者的一种类加载器实现的最佳实践。

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在程序的ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。如果读者有兴趣的话,可以尝试去写一个与rt.jar类库中已有类重名的Java类,将会发现它可以正常编译,但永远无法被加载运行。

双亲委派模型对于保证Java程序的稳定运作极为重要,但它的实现却异常简单,用以实现双亲委派的代码只有短短十余行,全部集中在java.lang.ClassLoaderloadClass()方法之中

-------《深入理解Java虚拟机》第三版 7.4.2章节

直接看sun.misc.Launcher.AppClassLoader#loadClass方法

public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
    int var3 = var1.lastIndexOf(46);
    if (var3 != -1) {
        SecurityManager var4 = System.getSecurityManager();
        if (var4 != null) {
            var4.checkPackageAccess(var1.substring(0, var3));
        }
    }

    //一般都是false,想要返回TRUE可能需要设置启动参数 lookupCacheEnabled 为true。
    // 为true时,具体的逻辑也是C++写的,所以做了什么就不大清楚了。https://www.jianshu.com/p/b95171b2527b
    if (this.ucp.knownToNotExist(var1)) {
        //如果这个类已经被这个类加载器加载,则返回这个 类,否则返回Null
        Class var5 = this.findLoadedClass(var1);
        if (var5 != null) {
            if (var2) {
                //如果该类没有被link(连接),则连接,否则什么都不做
                this.resolveClass(var5);
            }

            return var5;
        } else {
            throw new ClassNotFoundException(var1);
        }
    } else {//一般会走这里,直接看java.lang.ClassLoader#loadClass(java.lang.String, boolean)
        return super.loadClass(var1, var2);
    }
}

java.lang.ClassLoader#loadClass(java.lang.String, boolean),注意这个方法是个protected限定符,它还有个public

的重载方法java.lang.ClassLoader#loadClass(java.lang.String),这个方法里面调用了protected方法。

就是在这个方法里面实现的双亲委派

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        //先从缓存中查找该class对象,找到就直接返回,不用再去加载,
        //最终调用的是java.lang.ClassLoader#findLoadedClass0这个本地方法,调到C++中去了
        Class<?> c = findLoadedClass(name);
        if (c == null) {//如果是第一次加载
            long t0 = System.nanoTime();
            try {//下面就是双亲委派的逻辑,啥也不是
                if (parent != null) {//如果这个类加载器,有父加载器,对于ExtClassLoader,这个parent是null值,对于AppClassLoader,这个parent是ExtClassLoader
                    c = parent.loadClass(name, false);//对于AppClassLoader,则会调用到ExtClassLoader的loadClass方法,然鹅,ExtClassLoader并没重新实现自己的loadClass方法,所以又会调到java.lang.ClassLoader#loadClass(java.lang.String, boolean),相当于递归
                } else {//对于ExtClassLoader,它parent是null值,会调到这里,嗯,就是用bootstrap class loader去加载,最后会调用到java.lang.ClassLoader#findLoadedClass0这个C++方法
                    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();
                //则调用类加载们自定义的加载逻辑去加载类了,
                //对于这两个AppClassLoader和ExtClassLoader,都是URLClassLoader的子类,所以都会走到java.net.URLClassLoader#findClass方法
                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;
    }
}

java.net.URLClassLoader#findClass

protected Class<?> findClass(final String name)
    throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    //根据要被加载的类的全限定名,把全限定名中的.都转换成/,并加上class后缀
                    //如:com.example.classloader.MyClassLoaderTest会被转化成com/example/classloader/MyClassLoaderTest.class
                    String path = name.replace('.', '/').concat(".class");
                    //获取这个class文件的具体地址如:file:/E:/shihy/CodeOfWork/study-shihy/target/classes/com/example/classloader/MyClassLoaderTest.class
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            //根据资源地址,真正开始调用,java.net.URLClassLoader#defineClass
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

java.net.URLClassLoader#defineClass

private Class<?> defineClass(String name, Resource res) throws IOException {
    long t0 = System.nanoTime();
    int i = name.lastIndexOf('.');
    URL url = res.getCodeSourceURL();
    if (i != -1) {
        String pkgname = name.substring(0, i);
        // Check if package already loaded.
        Manifest man = res.getManifest();
        definePackageInternal(pkgname, man, url);
    }
    // Now read the class bytes and define the class
    java.nio.ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        // Use (direct) ByteBuffer:
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        //最终调用的方法,最后调用到C++代码
        return defineClass(name, bb, cs);
    } else {
        byte[] b = res.getBytes();
        // must read certificates AFTER reading bytes.
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        //最终调用的方法,最后调用到C++代码
        return defineClass(name, b, 0, b.length, cs);
    }
}

实现我们自己的类加载器:

双亲委派模型的第一次“被破坏”其实发生在双亲委派模型出现之前——即JDK 1.2面世以前的“远古”时代。由于双亲委派模型在JDK 1.2之后才被引入,但是类加载器的概念和抽象类java.lang.ClassLoader则在Java的第一个版本中就已经存在,面对已经存在的用户自定义类加载器的代码,Java设计者们引入双亲委派模型时不得不做出一些妥协,为了兼容这些已有代码,无法再以技术手段避免loadClass()被子类覆盖的可能性,**只能在JDK 1.2之后的java.lang.ClassLoader中添加一个新的protected方法findClass(),并引导用户编写的类加载逻辑时尽可能去重写这个方法,而不是在loadClass()中编写代码。**上面我们已经分析过loadClass()方法,双亲委派的具体逻辑就实现在这里面,按照loadClass()方法的逻辑,如果父类加载失败,会自动调用自己的findClass()方法来完成加载,这样既不影响用户按照自己的意愿去加载类,又可以保证新写出来的类加载器是符合双亲委派规则的。-------《深入理解Java虚拟机》第三版 7.4.3章节

由上面的加粗可知,JDK官方建议我们实现自己的类加载器的时候,希望我们保留双亲委派的规则,特意加了一个java.lang.ClassLoader#findClass方法,我们只要重写这个方法,就可以实现自己的类加载器了。这么一仔细看,java.net.URLClassLoader这个类加载器,就是一种自定义的类加载器,照着抄就得了。

public class MyClassLoaderTest {
    static class MyClassLoader extends ClassLoader {
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name
                                                      + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }

        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

    }

    public static void main(String args[]) throws Exception {
        //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
        MyClassLoader classLoader = new MyClassLoader("E:\\shihy\\CodeOfWork\\study-shihy\\target\\classes\\com\\example\\classloader");
        Class clazz = classLoader.loadClass("com.example.classloader.TestJDKClassLoader");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("test", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

2.3打破双亲委派

这个实现就很简单了,从2.2可知 ,打破双亲委派,只要重写protected的java.lang.ClassLoader#loadClass(java.lang.String, boolean)就可。

又因为是自定的类加载器,那么java.lang.ClassLoader#findClass还是要实现滴。

上面的自定义类加载器MyClassLoader稍微改造一下,重写个protected的java.lang.ClassLoader#loadClass(java.lang.String, boolean)就可以了,其他都一模一样。

public class MyDestroyClassLoaderTest {
    static class MyDestroyClassLoader extends ClassLoader {
        private String classPath;

        public MyDestroyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            name = name.replaceAll("\\.", "/");
            FileInputStream fis = new FileInputStream(classPath + "/" + name
                                                      + ".class");
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;

        }
        //这个方法没变
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }

        /**
         * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
         *
         * @param name
         * @param resolve
         * @return
         * @throws ClassNotFoundException
         */
        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 (!name.equals("com.example.classloader.TestJDKClassLoader")) {
                    c = super.loadClass(name,false);
                }

                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.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    }

    public static void main(String args[]) throws Exception {
        System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());

        MyDestroyClassLoader classLoader =
                new MyDestroyClassLoader("E:\\shihy\\CodeOfWork\\study-shihy\\target\\classes");
        //尝试用自己改写类加载机制去加载com.example.classloader.TestJDKClassLoader
        Class clazz = classLoader.loadClass("com.example.classloader.TestJDKClassLoader");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("test", null);
        method.invoke(obj, null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}

注意:上面两个自定义的类加载器,有一点没说,那就是,自定义的类加载器的父加载器默认是sun.misc.Launcher.AppClassLoader

因为自定义的类加载器父类是java.lang.ClassLoader,所以自定义的类加载器的构造方法里面,会默认调用父类的无参构造方法。

java.lang.ClassLoader#ClassLoader()

protected ClassLoader() {
    this(checkCreateClassLoader(), getSystemClassLoader());
}

查看java.lang.ClassLoader#getSystemClassLoader

public static ClassLoader getSystemClassLoader() {
    initSystemClassLoader();
    if (scl == null) {
        return null;
    }
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkClassLoaderPermission(scl, Reflection.getCallerClass());
    }
    //返回的就是AppClassLoader
    return scl;
}