Classloader源码解读

前文已经基本介绍类加载及类加载器,本文主要探讨其在JDK中具体实现机制

如何确认class被哪个classLoader加载?

我们知道类加载是有双亲委派机制的,具体基本如下图

如何加载类?

目前有三种方式:

  1. 命令行启动应用时候由JVM初始化加载 通过main()入口,加载一系列资源
  2. 通过Class.forName()方法动态加载
  3. 通过ClassLoader.loadClass()方法动态加载

代码如下:

java 复制代码
public static void main(String args[]) throws ClassNotFoundException {
    Class cl = Class.forName("com.hyw.algorithm.BoyerMooreTest");
    System.out.println(cl);
    cl = ClassloaderTest.class.getClassLoader().loadClass("com.hyw.algorithm.BoyerMooreTest");
    System.out.println(cl);
    //获取的是appclassloader
    cl = ClassLoader.getSystemClassLoader().loadClass("com.hyw.algorithm.BoyerMooreTest");
    System.out.println(cl);
}

关注点Class.forName和ClassLoader().loadClass区别:

  • Class.forName(): 将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
  • ClassLoader.loadClass(): 只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
  • Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。

双亲委派机制源码

具体在ClassLoader.java

java 复制代码
public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}
java 复制代码
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;
    }
}

Bootstrap ClassLoader不是Ext ClassLoader的父类,如果父类为空,默认就是Bootstrap ClassLoader

如何确定一个类被哪个classLoader加载?

可以用ClassloaderTest.class.getClassLoader()来判断

java 复制代码
ClassLoader classLoader = ClassloaderTest.class.getClassLoader();
System.out.println(classLoader);
while (Objects.nonNull(classLoader.getParent())) {
    classLoader = classLoader.getParent();
    System.out.println(classLoader);
}

在idea中执行结果如下:

bash 复制代码
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@7f31245a

一般在系统分析时,不太可能直接打印这个,可以根据如下来进行判断:

  • Bootstrap ClassLoader:负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的

启动参数环境变量 -Dsun.boot.class.path

  • Extension ClassLoader:由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器

启动参数 -Djava.ext.dirs=xxx

  • Application ClassLoader:该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

启动参数 -cp或者-classpath -Djava.class.path

系统默认几个ClassLoader具体实现是啥?

具体实现在sun.misc.Launcher

Bootstrap ClassLoader

启动时默认加载的

java 复制代码
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);
    }
}

Extension ClassLoader

java 复制代码
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) {
                    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 {
                    File[] var1 = Launcher.ExtClassLoader.getExtDirs();
                    int var2 = var1.length;

                    for(int var3 = 0; var3 < var2; ++var3) {
                        MetaIndex.registerDirectory(var1[var3]);
                    }

                    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 {
        super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
        SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
    }

    private static File[] getExtDirs() {
        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) {
                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 = var5.getAuthority() == null ? (new File(var5)).getParentFile() : 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;
    }
}

Application ClassLoader

java 复制代码
static class AppClassLoader extends URLClassLoader {
    final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

    public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
        final String var1 = System.getProperty("java.class.path");
        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);
                return new Launcher.AppClassLoader(var1x, var0);
            }
        });
    }

    AppClassLoader(URL[] var1, ClassLoader var2) {
        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();
    }
}

Laucher

上述几个classLoader在启动时候都会加载,默认给加载线程设置的classloader是APPClassLoader

java 复制代码
public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
        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);
    }

}

ExtClassLoader和AppClassLoader都是继承URLClassLoader

参考文档:
JVM 基础 - Java 类加载机制
一看你就懂,超详细java中的ClassLoader详解
ClassLoader资源加载机制

相关推荐
TT哇6 分钟前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
dessler21 分钟前
Docker-run命令详细讲解
linux·运维·后端·docker
火烧屁屁啦29 分钟前
【JavaEE进阶】初始Spring Web MVC
java·spring·java-ee
w_312345442 分钟前
自定义一个maven骨架 | 最佳实践
java·maven·intellij-idea
岁岁岁平安1 小时前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA1 小时前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
Q_19284999061 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
张国荣家的弟弟1 小时前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S1 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring