JVM类加载之双亲委派机制解读

 更新时间:2023年12月04日 09:26:58   作者:w7h1te  
这篇文章主要介绍了JVM类加载之双亲委派机制解读,类加载阶段分为加载、连接、初始化三个阶段,而加载阶段需要通过类的全限定名来获取定义了此类的二进制字节流,Java特意把这一步抽出来用类加载器来实现,需要的朋友可以参考下

前言

在面试过程中:也会被问到关于如何理解双亲委派模型这样的问题,接下来就通过这篇文章往明白了解一下。

我们首先需要了解一下类加载阶段

类的加载阶段

类加载阶段分为加载、连接、初始化三个阶段,而加载阶段需要通过类的全限定名来获取定义了此类的二进制字节流。Java特意把这一步抽出来用类加载器来实现。把这一步骤抽离出来使得应用程序可以按需自定义类加载器。并且得益于类加载器,OSGI、热部署等领域才得以在JAVA中得到应用。

类加载器除了能用来加载类,还能用来作为类的层次划分。Java自身提供了3种类加载器

类加载器

1、启动类加载器(Bootstrap ClassLoader),它是属于虚拟机自身的一部分,用C++实现的,主要负责加载<JAVA_HOME>\lib目录中或被-Xbootclasspath指定的路径中的并且文件名是被虚拟机识别的文件。它等于是所有类加载器的爸爸。

2、扩展类加载器(Extension ClassLoader),它是Java实现的,独立于虚拟机,主要负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量所指定的路径的类库。

3、应用程序类加载器(Application ClassLoader),它是Java实现的,独立于虚拟机。主要负责加载用户类路径(classPath)上的类库,如果我们没有实现自定义的类加载器那这玩意就是我们程序中的默认加载器。

什么是双亲委派机制

上图:

在这里插入图片描述

简单来说:如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。这里的双亲其实就指的是父类,没有mother。父类也不是我们平日所说的那种继承关系,只是调用逻辑是这样。

也就是当类加载器收到类加载的请求时候会首先调用父类(ClassLoader)的findLoadedClass方法,判断类是否加载过,如果已经加载过则直接返回,否则会将加载任务委托给成员变量parent,并不是指的父类。parent加载器在收到类加载请求后,也会先判断需要加载的类是否已经加载过,如果加载过则结束,否则也会将加载任务委托给成员变量parent去进行类加载。这里是一个循环过程,直到将加载任务委托给Bootstrap ClassLoader 结束,如果Bootstrap ClassLoader也没有找到则交给各个子类自己加载,一直到最后,如果没有任何类加载器能加载则会抛出ClassNotFoundException。

为什么要设计双亲委派机制?

  • 沙箱安全机制

自己写的java.lang.String.class类不会被加载,这样可以防止核心API库被随意篡改。 为了不让我们写String类,类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。而String类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的String,自己写的String类根本没有机会得到加载。

  • 避免类的重复加载

当父亲已经加载了该类, 就没必要子classLoader再加载一次,保证被加载的唯一性。

引申内容

JVM 类加载器和类本身一同确立类在Java虚拟机中的唯一性

问题:由不同类加载器加载同一个类,实例化为对象。使用instanceof判断该对象与该类的归属,请问结果是true还是false?

答案是false。

验证解析

import java.io.IOException;
import java.io.InputStream;
public class ClassLoaderTest {
    public static void main(String[] args) throws Exception {
        ClassLoader myLoader = new ClassLoader() {
            @SuppressWarnings("ResultOfMethodCallIgnored")
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
        Object obj = myLoader.loadClass("Practice.Java.ClassLoaderTest").newInstance();
        System.out.println(obj.getClass());
        System.out.println(obj instanceof Practice.Java.ClassLoaderTest);
    }
}

输出结果:

class Practice.Java.ClassLoaderTest
false

原因

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

结果分析

两行输出结果中,从第一句可以看出,这个对象确实是类Practice.Java.ClassLoaderTest实例化出来的对象,但从第二句可以发现,这个对象与类Practice.Java.ClassLoaderTest做所属类型检查的时候却返回了false。

这是因为虚拟机中存在了两个ClassLoaderTest类,一个是由系统应用程序类加载器加载的,另外一个是由我们自定义的类加载器加载的。虽然都来自同一个Class文件,但依然是两个独立的类,做对象所属类型检查时结果自然为false。

结论

Java类加载器这种特性可以简单的总结为命名空间。

即在 Java 虚拟机中,类的唯一性是由类加载器实例以及类的全名一同确定的。即便是同一串字节流,经由不同的类加载器加载,也会得到两个不同的类。

到此这篇关于JVM类加载之双亲委派机制解读的文章就介绍到这了,更多相关JVM双亲委派机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中的形式参数和实际参数案例详解

    Java中的形式参数和实际参数案例详解

    这篇文章主要介绍了Java中的形式参数和实际参数,形参和实参间的关系,两者是在调用的时候进行结合的,通常实参会将取值传递给形参,形参去之后进行函数过程运算,然后可能将某些值经过参数或函数符号返回给调用者,需要的朋友可以参考下
    2023-10-10
  • Java Spring Cloud 负载均衡详解

    Java Spring Cloud 负载均衡详解

    这篇文章主要介绍了Spring Cloud负载均衡及远程调用实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2021-09-09
  • Scala文件操作示例代码讲解

    Scala文件操作示例代码讲解

    本文章向大家介绍Scala 学习笔记之文件操作,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下
    2023-04-04
  • Spring boot 无法注入service问题

    Spring boot 无法注入service问题

    这篇文章主要介绍了Spring boot 无法注入service问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • 微服务通过Feign调用进行密码安全认证操作

    微服务通过Feign调用进行密码安全认证操作

    这篇文章主要介绍了微服务通过Feign调用进行密码安全认证操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • 初识Java设计模式适配器模式

    初识Java设计模式适配器模式

    这篇文章主要为大家详细介绍了Java设计模式适配器模式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • SpringBoot 如何实现Session共享

    SpringBoot 如何实现Session共享

    这篇文章主要介绍了SpringBoot 如何实现Session共享,帮助大家更好的理解和学习spring boot框架,感兴趣的朋友可以了解下
    2020-09-09
  • 通过实例了解Java jdk和jre的区别

    通过实例了解Java jdk和jre的区别

    这篇文章主要介绍了通过实例了解Java jdk和jre的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • SpringBoot多环境打包与配置文件排除实践记录

    SpringBoot多环境打包与配置文件排除实践记录

    本文介绍了SpringBoot项目多环境打包与配置文件排除实践,包括多环境配置的实现方法、打包时排除配置文件的方法以及动态加载外部配置文件的最佳实践,感兴趣的朋友跟随小编一起看看吧
    2024-11-11
  • Java计时新姿势StopWatch的使用方法详解

    Java计时新姿势StopWatch的使用方法详解

    这篇文章主要给大家介绍了关于Java计时新姿势StopWatch的相关资料,以及java 中使用StopWatch来计算时间差的方法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-01-01

最新评论