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资源加载机制

相关推荐
风象南4 小时前
我把大脑开源给了AI
人工智能·后端
哈里谢顿8 小时前
1000台裸金属并发创建中的重难点问题分析
面试
哈里谢顿8 小时前
20260303面试总结(全栈)
面试
橙序员小站9 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
怒放吧德德9 小时前
Netty 4.2 入门指南:从概念到第一个程序
java·后端·netty
雨中飘荡的记忆11 小时前
大流量下库存扣减的数据库瓶颈:Redis分片缓存解决方案
java·redis·后端
开心就好202512 小时前
UniApp开发应用多平台上架全流程:H5小程序iOS和Android
后端·ios
悟空码字12 小时前
告别“屎山代码”:AI 代码整洁器让老项目重获新生
后端·aigc·ai编程
小码哥_常12 小时前
大厂不宠@Transactional,背后藏着啥秘密?
后端
奋斗小强12 小时前
内存危机突围战:从原理辨析到线上实战,彻底搞懂 OOM 与内存泄漏
后端