从sofa-ark谈java类加载机制

31 阅读2分钟

双亲委派模型

JVM 有 3 个classloader:

  • AppClassLoader 也叫 SystemClassLoader
  • ExtClassLoader,在 java9 被废弃,使用 PlatformClassLoader。用于加载在JDK安装目录下的 jre/lib/ext 目录。
  • BootstrapClassLoader。这个是 native 的 class loader。通过 java 无法获取到该 class loader。即通过PlatformClassLoader的getParent()会是 null。用于加载jar/lib目录。
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("AppClassLoader: " + appClassLoader);

// 获取平台类加载器
// ClassLoader platformClassLoader = ClassLoader.getPlatformClassLoader();
// System.out.println("PlatformClassLoader: " + platformClassLoader);

// 获取扩展类加载器。java9之前和之后的返回结果会有不同
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println("ExtClassLoader: " + extClassLoader);

// 获取启动类加载器(BootstrapClassLoader)
// 注意:BootstrapClassLoader 是用本地代码实现的,Java 中获取不到它的实例,
// 因此当请求它的父类加载器时会返回 null
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println("BootstrapClassLoader: " + bootstrapClassLoader);

一个常见的面试题,如果在应用程序中定义了一个java.lang.String类,是不会加载这个自定义的 String 类的。因为,JVM 使用了双亲委派机制。

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

sofa ark

sofa ark 中,在 JVM 之上,构造了 ArkContainer。在 ArkContainer 中,构造了 BizClassLoader,通过 BizClassLoader 重新加载了业务 main 类。因此,业务类一直都在 BizClassLoader 中。

在 java 中,ClassA 如果依赖了 ClassB,如果 ClassA 的 classLoader 是classLoaderBiz,那么在加载 ClassB 时,也会先用是classLoaderBiz的 loadClass方法进行加载。注意这里说的是classLoaderBiz的 loadClass方法,而不是classLoaderBiz这个 class loader。因为如果在classLoaderBiz使用了双亲委派,那么可能被其父加载器加载。

在 sofa ark 中,分为 BizClassLoader 和 PluginClassLoader。

image.png

PlunginClassLoader 需要将对外暴露的类、 package、resource 通过 export 的方式暴露出去。

在使用 BizClassLoader 进行加载的时候会判断是否为 export 决定是否使用该export的 class loader。


    @Override
    protected Class<?> loadClassInternal(String name, boolean resolve) throws ArkLoaderException {
        Class<?> clazz = null;

        // 0. sun reflect related class throw exception directly
        if (classloaderService.isSunReflectClass(name)) {
            throw new ArkLoaderException(
                String
                    .format(
                        "[ArkBiz Loader] %s : can not load class: %s, this class can only be loaded by sun.reflect.DelegatingClassLoader",
                        bizIdentity, name));
        }

        // 1. findLoadedClass
        if (clazz == null) {
            clazz = findLoadedClass(name);
        }

        // 2. JDK related class
        if (clazz == null) {
            clazz = resolveJDKClass(name);
        }

        // 3. Ark Spi class
        if (clazz == null) {
            clazz = resolveArkClass(name);
        }

        // 4. pre find class
        if (clazz == null) {
            clazz = preLoadClass(name);
        }

        // 5. Plugin Export class
        // 本文重点关注这里
        if (clazz == null) {
            clazz = resolveExportClass(name);
        }

        // 6. Biz classpath class
        if (clazz == null) {
            clazz = resolveLocalClass(name);
        }

        // 7. Java Agent ClassLoader for agent problem
        if (clazz == null) {
            clazz = resolveJavaAgentClass(name);
        }

        // 8. post find class
        if (clazz == null) {
            clazz = postLoadClass(name);
        }

        if (clazz != null) {
            if (resolve) {
                super.resolveClass(clazz);
            }
            return clazz;
        }

        throw new ArkLoaderException(String.format("[ArkBiz Loader] %s : can not load class: %s",
            bizIdentity, name));
    }

参考