对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,如果两个类来自同一份class文件,被一个虚拟机加载,但是是由两个加载器加载,它们也是不相等的。

从虚拟机角度看,只存在两种不同的类加载器,一种是启动类加载器(Bootstrap ClassLoader),这个加载器由C++实现,是虚拟机自身的一部分;另一中就是其它的类加载器,这些类加载器都由Java语言实现,并全部继承自java.lang.ClassLoader。

类加载器种类

启动类加载器

启动类加载器负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数锁指定的灵境中存放的,且Java虚拟机能够识别的类库加载到内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可。

扩展类加载器

扩展类加载器(Extension Class Loader)是在类sun.misc.Lanucher$ExtClassLoader中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。

应用程序类加载器

应用程序类加载器(Application Class Loader)由sun.misc.Lanucher$AppClassLoader来实现。由于应用程序类加载器时ClassLoader类中的getSystemClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径下(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。在应用程序没有定义自己的类加载器的情况下,一般情况下这个就是程序默认的类加载器。

双亲委派模型

各种类加载器之间的层次关系被称为类加载器的“双亲委派模型”。双亲委派模型除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合(Composition)的关系来复用父加载器的代码。

双亲委派的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去常识加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它 的搜索范围中没有找到所需的类)时,子加载器裁会尝试自己去完成加载。

image-20220402154219541

双亲委派模型的一个显著的好处是Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如java.lang.Object,它存放在rt.jar中,无论哪一个类加载器加载这个类,最终都是委派给处于模型最顶层的启动类加载器进行加载,因为Object类在程序的各种类加载环境中都能保证是同一个类。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查请求的类是否被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果父类加载器抛出ClassNotFoundException
                    // 说明父类加载器无法完成加载请求
                }
                if (c == null) {
                    // 如果父类无法加载时,调用自身的findClass方法进行加载
                    long t1 = System.nanoTime();
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

为什么打破双亲委派模型

在某些场景下,需要优先使用当前类加载器加载符合范围内的类,而不是依次继续向上传递,这种情况下需要打破双亲委派模型。

如何打破双亲委派模型

自定义ClassLoader类加载器,重写loadClass方法(不依照往上开始寻找类加载器),就算是打破双亲委派机制。

打破双亲委派模型的例子

Tomcat

Tomcat给每个Web应用穿紧固件一个类加载实例(WebAppClassLoader),改加载器重写了loadClass方法,优先加载当前应用目录下的类,如果找不到,才一层一层往上找。

热部署
posted on   misterD  阅读(259)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了



点击右上角即可分享
微信分享提示