ClassLoader中那些傻傻分不清的概念

5 篇文章 2 订阅
1 篇文章 0 订阅

ClassLoader有几种

根加载器

你的java程序想要运行,就必须有一个运行环境,这个运行环境既包括底层的jvm支持,还包括基础的jre类库支持。那么这些基础的jre类本省也是java的class,所以这些class的加载就是由native代码实现的bootstrap class loader来加载的。
根加载器主要加载的是%JAVA_HOME%/jre/lib/.jar,%JAVA_HOME%/jre/lib/classes/,以及环境变量sun.boot.class.path对应目录下的类文件。

ext加载器

ExtClassLoader,用于加载%JAVA_HOME%/jre/lib/ext/.jar,%JAVA_HOME%/jre/lib/ext/classes/,以及环境变量java.ext.dirs对应目录下的类文件。

app加载器

AppClassLoader,主要用于加载用户目录下的类文件,以及环境变量java.class.path对应目录下的类文件。该加载器也是java程序默认的加载器。

自定义的加载器

应用程序是可以通过继承ClassLoader类实现自己的加载器,来自定义类的加载路径、处理策略等。我们经常使用的组件和框架大多都有自己自定义的类加载器,例如:tomcat、spring等。

父加载器(上层加载器)

父加载器的作用,是类加载的委派机制所需。自定义的加载器在实例化时可以指定自己的父加载器。
要明确以下两点

  1. 父加载器 ≠ 父类。ClassLoader是一个抽象类,内部有一个私有的ClassLoader类型的变量parent。这个就指的是当前类加载器的父加载器。(在本文后面的流程图中称作:上层加载器)
  2. 父加载器 ≠ 加载自己的加载器。假设有两个自定义的加载器A和B,A可以指定自己的父加载器为B,但是加载A类的加载器很可能是AppClassLoader,未必是B(但是你可以这样做)。

要理解以上这两点,我们可以看下下面这个小程序。

public class MyClassLoaderA extends  ClassLoader {

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

    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println("1.MyClassLoaderA 's classloader is:\n\t" + MyClassLoaderA.class.getClassLoader());
        System.out.println("2.MyClassLoaderA 's parent is:\n\t" + new MyClassLoaderA().getParent());
        System.out.println("3.MyClassLoaderA 's classloader 's parent is:\n\t" + MyClassLoaderA.class.getClassLoader().getParent());
        System.out.println("4.MyClassLoaderA 's classloader 's grand parent is:\n\t" + MyClassLoaderA.class.getClassLoader().getParent().getParent());
        System.out.println("5.AppClassLoader 's classloader is:\n\t" + Class.forName("sun.misc.Launcher$AppClassLoader").getClassLoader());
        System.out.println("6.ExtClassLoader 's classloader is:\n\t" + Class.forName("sun.misc.Launcher$ExtClassLoader").getClassLoader());
    }
}

最终的输出如下:

1.MyClassLoaderA 's classloader is:
	sun.misc.Launcher$AppClassLoader@18b4aac2
2.MyClassLoaderA 's parent is:
	sun.misc.Launcher$AppClassLoader@18b4aac2
3.MyClassLoaderA 's classloader 's parent is:
	sun.misc.Launcher$ExtClassLoader@74a14482
4.MyClassLoaderA 's classloader 's grand parent is:
	null
5.AppClassLoader 's superclass  is:
	class java.net.URLClassLoader
6.AppClassLoader 's classloader is:
	null
7.ExtClassLoader 's classloader is:
	null

以AppClassLoader为例:

  • 他的父类是URLClassLoader
  • 他的父加载器是ExtClassLoader
  • 他也不是被ExtClassLoader加载的,他和ExtClassLoader都是被根加载器加载的。

ClassLoader的类加载流程

相信你一定听说过双亲委派模型,双亲委派这个概念其实蛮抽象的。我觉得不必太纠结这个概念的含义,重点是理解jdk内部实现流程即可。
在这里插入图片描述

  • 最上层的代表根加载器,由于根加载器是本地代码实现的,jdk里观察不到源码,所以其内部处理流程是大概猜想应该是这样子。
  • CustomClassLoader是假设有这么一个自定义的类加载器,如果通过他加载一个类,那么整个流程就应该是如图所示。
  • 你会发现CustomClassLoader、AppClassLoader、ExtClassLoader的内部处理流程是完全一致的。这是因为jdk的ClassLoader内部实现上采用了模板方法模式,所有类加载器ClassLoader类的子类,都是通过loadClass方法来加载类,而loadClass的方法实现流程已经在ClassLoader内部固化成了模板代码,每个加载器只需要自己实现findClass方法即可。findClass方法要干的事情,就对应我们图中的《自己去找》节点。也就是说,每个继承自ClassLoader的类都只需要完成自己的类查找逻辑即可。

最后附上loadClass方法的源码,方面对照理解。

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
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值