Java8之类加载机制class源码分析

参考资料:

《Java的类加载器与双亲委托机制》

前文:

《Java8之类的加载》

写在开头:本文为学习后的总结,可能有不到位的地方,错误的地方,欢迎各位指正。

目录

一、loadClass

二、getParent

三、findClass

四、defineClass


        在前文中,对类加载机制的一些流程以及原理做了一定了解,这篇文章我们深入到ClassLoader类的源码中进一步深入了解。

一、loadClass

        类加载器的加载时通过loadClass方法实现的,改方法也是双亲委派机制实现的入口,在之前的文章中我们讲过,除了顶层的BootstrapClassLoader,其余类加载器都会先调用父加载器尝试查找加载缓存,如果所有父加载器中都没有才会调用自身的findClass方法加载该class。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //首先,根据name检查类是否已经加载,若已加载,会直接返回
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //若当前类加载器有父加载器,则调用其父加载器的loadClass()
                    c = parent.loadClass(name, false);
                } else {
                    //若当前类加载器的parent为空,则调用findBootstrapClassOrNull()
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
            }
        
            if (c == null) {
                // 1.如果到这里c依然为空的话,表示一直到最顶层的父加载器也没有找到已加载的c,那就会调用findClass进行查找
                // 2.在findClass的过程中,如果指定目录下没有,就会抛出异常ClassNotFoundException
                // 3.抛出异常后,此层调用结束,接着其子加载器继续进行findClass操作
                long t1 = System.nanoTime();
                c = findClass(name);

                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

        根据上面的源码,我们可以看到当缓存中没有该class时,如果parent不为空则会调用parent.loadClass方法从父加载器中查询,否则,直接调用findBootstrapClass方法,这是因为只有ExtClassLoader加载器的parent为空(因为Bootstrap ClassLoader是有C++编写的,并不是一个Java类。)我们可以通过getParent方法进行验证。

    public static void main(String[] args) {
        ClassLoader loader = Person.class.getClassLoader();
        while (loader != null) {
            System.out.println(loader);
            loader = loader.getParent();
        }
    }

         结果如下:

         而findBootstrapClassOrNull方法观察源码发现,其在对name进行校验后,最终调用了一个native方法findBootstrapClass()。在findBootstrapClass()方法中最终会用Bootstrap Classloader来查找类。

private Class<?> findBootstrapClassOrNull(String name)
{
    if (!checkName(name)) return null;
    return findBootstrapClass(name);
}
    
private native Class<?> findBootstrapClass(String name);

二、getParent

        在loadClass方法解析中我们使用了getParent尝试获取了父加载器,这里来解析一下getParent方法。我们看到其返回值有两种可能,为空或者是parent变量。

public final ClassLoader getParent() {
    if (parent == null) return null;
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkClassLoaderPermission(parent, Reflection.getCallerClass());
    }
    return parent;
}

        我们溯源一下parent的源头,找到了被private修饰的构造方法。

private ClassLoader(Void unused, ClassLoader parent) {
    this.parent = parent;
    ... 
}

         于是我们再溯源一下该私有构造方法的调用源头,于是接着查找到了另外两个构造方法。

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

        发现可以在调用ClassLoder的构造方法时,指定一个parent。若没有指定的话,会使用getSystemClassLoader()方法的返回值。于是继续溯源getSystemClassLoader方法。

public static ClassLoader getSystemClassLoader() {
    initSystemClassLoader();
    if (scl == null) {
        return null;
    }
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkClassLoaderPermission(scl, Reflection.getCallerClass());
    }
    return scl;
}

         getSystemClassLoader方法首先会调用initSystemClassLoader方法对scl变量初始化。

private static synchronized void initSystemClassLoader() {
    if (!sclSet) {
        if (scl != null)
            throw new IllegalStateException("recursive invocation");
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); //1
        if (l != null) {
            Throwable oops = null;
            scl = l.getClassLoader();
            ...
        }
        sclSet = true;
    }
}

         继续溯源到initSystemClassLoader方法中,我们发现scl是获取的Launcher类的对象,然后调用了Launcher类的getClassLoader()方法。

private static synchronized void initSystemClassLoader() {
    if (!sclSet) {
        if (scl != null)
            throw new IllegalStateException("recursive invocation");
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); //1
        if (l != null) {
            Throwable oops = null;
            scl = l.getClassLoader();
            ...
        }
        sclSet = true;
    }
}
	// Launcher.java
    public static Launcher getLauncher() {
		return launcher;
	}

         最终,我们在Launcher类中找到了根源,其值正是Launcher类中的AppClassLoader。

Launcher类
---------
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);
    }
    ...
}

        于是,我们得出在创建ClassLoder时有以下结论:

        (1)可以指定一个ClassLoder作为其parent,也就是其父加载器。
        (2)若没有指定的话,会使用getSystemClassLoader()方法的返回值(也就是Launcher类中的AppClassLoader)作为其parent。
        (3)通过getParent()方法可以获取到这个父加载器。

三、findClass

        findClass()方法一般被loadClass()方法调用去加载指定名称类。

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
} 

        我们发现ClassLoader类中并没有具体的逻辑,这也就意味着该方法的具体实现是由其子类实现的,在前文中我们介绍过ExtClassLoader和AppClassLoader都继承自URLClassLoader,这里我们以URLClassLoader作为样例进行分析。

URLClassLoader类
---------
protected Class<?> findClass(final String name) throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    String path = name.replace('.', '/').concat(".class");
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                        ...
    return result;
}

private Class<?> defineClass(String name, Resource res) throws IOException {
    ...
    URL url = res.getCodeSourceURL();
    ...
    java.nio.ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        ...
        return defineClass(name, bb, cs);
    } else {
        byte[] b = res.getBytes();
        ...
        return defineClass(name, b, 0, b.length, cs);
    }
}

        可以看到其对传入的name进行处理后,就调用了defineClass(name, res);在这个方法里主要是通过res资源和url,加载出相应格式的文件,最终还是通过ClassLoader的defineClass方法加载出具体的类。

四、defineClass

        能将class二进制内容转换成Class对象,如果不符合要求的会抛出异常,例如ClassFormatError、NoClassDefFoundError。如果是自定义ClassLoader,需要先将特定的文件读取成byte[]对象,再使用此方法,将其转为class对象。

/**
* String name:表示预期的二进制文件名称,不知道的话,可以填null。
* byte[] b:此class文件的二进制数据
* int off:class二进制数据开始的位置
* int len:class二进制数据的总长度
*/

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
    throws ClassFormatError
{
    return defineClass(name, b, off, len, null);
}


protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java类加载机制是指将类的字节码文件加载到内存中,并在运行时将其转换为可执行的代码的过程。Java类加载机制遵循了一定的规则和顺序,可以分为以下几个步骤: 1. 加载:类加载的第一步是加载,即将类的字节码文件加载到内存中。Java的类加载器负责从文件系统、网络或其他来源加载类的字节码文件。加载过程中会进行词法和语法的验证,确保字节码文件的正确性。 2. 链接:类加载的第二步是链接,即将已经加载的类与其他类或者符号进行关联。链接分为三个阶段: - 验证:验证阶段确保类的字节码文件符合Java虚拟机规范,包括检查文件格式、语义验证等。 - 准备:准备阶段为静态变量分配内存空间,并设置默认初始值。 - 解析解析阶段将符号引用转换为直接引用,例如将类或者方法的符号引用解析为对应的内存地址。 3. 初始化:初始化是类加载的最后一步,在此步骤中会执行类的初始化代码,对静态变量进行赋值和执行静态代码块。类的初始化是在首次使用该类时触发的,或者通过反射方式调用`Class.forName()`方法来强制初始化。 Java类加载机制是动态的,可以根据需要加载和卸载类,它还支持类的继承、接口实现、内部类等特性。类加载机制Java语言的重要特性之一,它为Java提供了强大的动态性和灵活性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值