JDK源码(十七):ClassLoader

顾名思义,类加载器(ClassLoader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

ClassLoader是负责加载类的对象。ClassLoader是一个抽象类。给定类的二进制名称,ClassLoader应尝试查找或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。

每个Class类对象都包含一个Class.getClassLoader()引用,指向定义它的ClassLoader。

数组类的对象不是由ClassLoader创建的,而是根据Java运行时的要求自动创建的。由Class.getClassLoader()返回的数组类的ClassLoader与元素类型的ClassLoader相同;如果元素类型是基元类型,则数组类没有ClassLoader。

应用程序实现ClassLoader的子类,以扩展Java虚拟机动态加载类的方式。ClassLoader通常可由安全管理器用于指定安全域。

ClassLoader类使用委托模型来搜索类和资源。ClassLoader的每个实例都有一个相关的父ClassLoader。当请求查找类或资源时,ClassLoader实例将在试图查找类或资源本身之前,将对该类或资源的搜索委托给其父ClassLoader。虚拟机的内置ClassLoader称为“引导类加载器”,它本身没有父类,但可以充当ClassLoader实例的父类。

支持类的并发加载的ClassLoader称为支持并行的ClassLoader,需要在类初始化时通过调用{ClassLoader.registeraspallelcapable}方法注册它们自己。注意,默认情况下,ClassLoader类注册为支持并行。但是,如果其子类具有并行能力,则仍需要注册它们自己。在委托模型不是严格分层的环境中,ClassLoader需要具有并行能力,否则类装入可能导致死锁,因为装入器锁在类装入过程的持续时间内保持。

通常,Java虚拟机以依赖于平台的方式从本地文件系统加载类。例如,在UNIX系统上,虚拟机从由CLASSPATH环境变量定义的目录加载类。

但是,有些类可能不是源于文件;它们可能源于其他源,例如网络,或者可以由应用程序构造。方法{defineClass(String,byte[],int,int)}将字节数组转换为类的实例。这个新定义的类的实例可以使用{class.newInstance}创建。

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:

  1. 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader

  2. 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。

  3. 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过  getParent()方法可以得到。

public static void main(String[] args) {
        ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
        if (classLoader != null){
            System.out.println(classLoader.getParent().toString());
            //输出:sun.misc.Launcher$ExtClassLoader@379619aa
        }
    }

ClassLoader中方法很多,但是与加载类相关的方法有下面这个几个:

getParent()

@CallerSensitive
    public final ClassLoader getParent() {
        if (parent == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // 权限检查
            checkClassLoaderPermission(parent, Reflection.getCallerClass());
        }
        return parent;
    }

loadClass(String name)

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    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 {
						//返回引导类加载器加载的类;如果找不到,则返回null
                        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();
					//findClass需要重写
                    c = findClass(name);

                    //这是定义类装入器;记录统计信息
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
				//链接指定的 Java 类
                resolveClass(c);
            }
            return c;
        }
    }

findClass(String name)

这个方法需要子类进行重写。

findLoadedClass(String name)

protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }

    private native final Class<?> findLoadedClass0(String name);

defineClass(String name, byte[] b, int off, int len)

    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;
    }

开发自己的类加载器

public class CustomClassLoader extends ClassLoader {

        private String rootDir;

        public CustomClassLoader(String rootDir) {
            this.rootDir = rootDir;
        }
        //重写findClass
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] classData = getClassData(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            }
            else {
                return defineClass(name, classData, 0, classData.length);
            }
        }

        private byte[] getClassData(String className) {
            String path = classNameToPath(className);
            //获取文件中的类内容

            //获取文件中的类内容
            return null;
        }

        private String classNameToPath(String className) {
            return rootDir + File.separatorChar
                    + className.replace('.', File.separatorChar) + ".class";
        }
    }

类加载器与web容器

对于运行在 Java EE™容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以  Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。

绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:

  • 每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes和 WEB-INF/lib目录下面。

  • 多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。

  • 当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。

更多精彩内容请关注微信公众号:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐楠_01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值