ClassLoader源码解读

本文详细介绍了ClassLoader的加载等级,包括BootStrap ClassLoader、ExtClassLoader和AppClassLoader,并探讨了ClassLoader的类层次结构。文章还解析了源码中的关键方法,如loadClass,揭示了双亲委托加载模型的工作原理。最后,讨论了如何自定义ClassLoader,指出通常只需重写findClass方法,而要打破双亲委托机制则需修改loadClass。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在进入阅读源码之前,首先要来了解下ClassLoader的两个基本问题:

ClassLoader的加载等级

BootStrap ClassLoader

启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库。这个ClassLoader完全是由JVM自己控制的,别人无法访问到这个类,所以它不遵循委托机制,没有子加载器。
通过下面代码获取BootStrap ClassLoader和ExtClassLoader负责加载的文件:

public static void main(String[] args) {
        String pathBoot = System.getProperty("sun.boot.class.path");
        System.out.println(pathBoot.replaceAll(":", System.lineSeparator()));

        System.out.println("--------------------");
        String pathExt = System.getProperty("java.ext.dirs");
        System.out.println(pathExt.replaceAll(":", System.lineSeparator()));
    }

输出:

/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/resources.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/rt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/sunrsasign.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/jsse.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/jce.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/charsets.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/jfr.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/classes
--------------------
/Users/xiaoshan/Library/Java/Extensions
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/ext
/Library/Java/Extensions
/Network/Library/Java/Extensions
/System/Library/Java/Extensions
/usr/lib/java

ExtClassLoader

扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。

AppClassLoader

系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

ClassLoader的类层次

源码阅读

下面看下ClassLoader类中的主要属性和方法
(1)可以看出这里每个类加载器的父加载器是在类内部的引用关系,并非继承关系。后面会写这个属性值设定的具体过程。

    private final ClassLoader parent;

(2)加载指定名称的类,loadclass方法内实现了通过双亲委托机制加载类。

    protected Class<?> loadClass(String name, boolean resolve){}

(3)查找指定名称的类,ClassLoader类中只是定义了该方法,如果需要实现自定义类加载器直接重写该方法即可。

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

(4)将相应的.class文件解析JVM能够识别的Class对象

    protected final Class<?> defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain){}

接下来看一下loadclass方法的具体内容:

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

平常听来无比高大上的双亲委托加载模型就是这般平易近人。
继续来看看parent属性值的设定过程:
先从Launcher类看起,Launcher类是Java应用的入口,是由BootStrapClassLoader加载的,我们只看它的前一部分代码。

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

        try {
        	//初始化AppClassLoader时传入ExtClassLoader的实例作为参数
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
		//设置AppClassLoader为线程上下文类加载器
        Thread.currentThread().setContextClassLoader(this.loader);
    }

Launcher完成对ExtClassLoader和AppClassLoader的初始化,甚至这两个类是以内部类的方式定义在Launcher类中的。初始化AppClassLoader时传入ExtClassLoader的实例作为参数,

AppClassLoader(URL[] var1, ClassLoader var2) {
            super(var1, var2, Launcher.factory);
            this.ucp.initLookupCache(this);
        }

追踪到这里传给AppClassLoader的构造方法中第二个参数,并不断向上调用父类构造方法,最终将AppClassLoader类对象的parent值设为ExtClassLoader。

自定义ClassLoader

public class MyClassLoader extends ClassLoader{
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    	//根据传入的包名解析后找到对应路径下的class文件
        File f = new File("c:/test/", name.replace(".", "/").concat(".class"));
        try {
            FileInputStream fis = new FileInputStream(f);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;

            while ((b=fis.read()) !=0) {
                baos.write(b);
            }

            byte[] bytes = baos.toByteArray();
            baos.close();
            fis.close();
            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name); 
    }
}

一般自定义类加载器只需要重写findclass方法即可,如果需要打破双亲委托机制就得重写loadclass方法。可以研究tomcat自定义类加载器的写法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值