跳至主要內容

类加载器的核心源码

梁晨阳2024年11月29日大约 3 分钟JVM类加载器

类加载器的加载类的方法

loadClass()方法是类加载的入口,提供了双亲委派机制,它会检查类是否被加载,以及交给父类加载或者交给启动类加载此类,如果都失败,则执行自己的加载方法,主要是调用findClass方法

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //首先,检查类是否被加载了
        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) {
                //如果仍然没有被加载,则调用findClass方法找到该类
                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) {
            //如果传递的resolve是true,则会执行生命周期中的连接阶段
            resolveClass(c);
        }
        return c;
    }
}

findClass()方法的主要目的是查找指定路径的资源:资源找到之后执行defineClass()方法

protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
    final Class<?> result;
    try {
        //AccessController.doPrivileged:以受信任的权限上下文执行代码,避免安全管理器阻止某些操作
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<>() {
                public Class<?> run() throws ClassNotFoundException {
                    //将类名转换为路径格式:
                    String path = name.replace('.', '/').concat(".class");
                    //从类路径中查找指定路径的资源
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            //资源查找成功则执行defineClass()方法;
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        } catch (ClassFormatError e2) {
                            if (res.getDataError() != null) {
                                e2.addSuppressed(res.getDataError());
                            }
                            throw e2;
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

defineClass() 方法的具体实现如下,负责将字节码定义为 Java 的 Class 对象

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();
        if (getAndVerifyPackage(pkgname, man, url) == null) {
            try {
                if (man != null) {
                    //定义类所在的包信息
                    definePackage(pkgname, man, url);
                } else {
                    definePackage(pkgname, null, null, null, null, null, null, null);
                }
            } catch (IllegalArgumentException iae) {
                // parallel-capable class loaders: re-verify in case of a
                // race condition
                if (getAndVerifyPackage(pkgname, man, url) == null) {
                    // Should never happen
                    throw new AssertionError("Cannot find package " +
                                             pkgname);
                }
            }
        }
    }
    // Now read the class bytes and define the class
    //从资源中获取类的字节码
    java.nio.ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        // 获取代码签名者信息
        CodeSigner[] signers = res.getCodeSigners();
        //获取类的来源信息
        CodeSource cs = new CodeSource(url, signers);
        PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        //调用重载方法,使用ByteBuffer定义类
        return defineClass(name, bb, cs);
    } else {
        byte[] b = res.getBytes();
        // 必须在读取字节后读取证书。
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        //调用重载方法:使用字节数组定义类
        return defineClass(name, b, 0, b.length, cs);
    }
}

defineClass()重载的方法中的操作:使用字节码将类加载到 JVM 的内存中,并返回 Class 对象。它通过调用底层的本地方法实现具体的类定义,同时提供了一些钩子方法(如 preDefineClass 和 postDefineClass)以便在定义类之前和之后执行额外逻辑。

protected final Class<?> defineClass(String name, java.nio.ByteBuffer b,
                                     ProtectionDomain protectionDomain)
throws ClassFormatError
{
    int len = b.remaining();

    // Use byte[] if not a direct ByteBuffer:
    if (!b.isDirect()) {
        if (b.hasArray()) {
            return defineClass(name, b.array(),
                               b.position() + b.arrayOffset(), len,
                               protectionDomain);
        } else {
            // no array, or read-only array
            byte[] tb = new byte[len];
            b.get(tb);  // get bytes out of byte buffer.
            return defineClass(name, tb, 0, len, protectionDomain);
        }
    }

    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass2(this, name, b, b.position(), len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

Your primary language is en-US, do you want to switch to it?

您的首选语言是 en-US,是否切换到该语言?