This website requires JavaScript.

ClassLoader之父类委派

1 2021-12-23 15:40:10 21

一、什么是ClassLoader

ClassLoader在Java中有非常重要的作用,它主要工作在Class装载的加载阶段,其主要的作用是从系统外部获得Class二进制数据流。

它是Java的核心组件,所有的class都是由ClassLoader进行加载的,ClassLoader负责将Class文件里的二进制数据装载进系统,然后交给Java虚拟机进行连接,初始化等操作。

二、ClassLoader种类

名称 作用
启动类加载器(Bootstrap ClassLoader) C++编写,在java里无法获取,加载核心类库(JAVA_HOME/lib 如rt.jar)
扩展类加载器(Extension ClassLoader) Java编写,可以在java里获取,加载扩展类(JAVA_HOME/jre/lib/ext)
系统类加载器/应用程序类加载器(Application ClassLoader) 是与我们接触对多的类加载器,我们写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader返回的就是它。加载用户类路径ClassPath下的类
自定义ClassLoader Java编写,定制化加载,继承java.lang.ClassLoader

三、ClassLoader的双亲委派机制 父类委派机制(parent-delegation model)

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

Java中任意一个类,都需要由加载它的类加载器和这个类本身来一同确立其在Java虚拟机中的唯一性,判断一个类是否相同,通常用equals()方法,isInstance()方法和isAssignableFrom()方法。来判断,对于同一个类,如果没有采用相同的类加载器来加载,在调用的时候,会产生意想不到的结果;

如果不是同一个类加载器加载,即时是相同的class文件,也会出现判断不想同的情况,从而引发一些意想不到的情况,为了保证相同的class文件,在使用的时候,是相同的对象,jvm设计的时候,采用了双亲委派的方式来加载类,同时也避免多份同样字节码的加载:类似于单例模式,多个ClassLoader只需要加载一个类字节码即可。形成一个整体的一种机制。 > 即有两个类来源于同一个 Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等

通过这样设计Java中需要的基础类无论是被谁加载最终都会往上传递由指定的类加载器加载,保证了内存中基础类只存在一种,比如Object类在程序的各种类加载器环境中都能够保证是同一个类!

1、先看案例

假想我现在篡改核心类String,如果没有父类委派机制,那么我自己定义一个类植入错误程序就可以使得整个系统崩溃。

        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
package java.lang; // 执行下列方法会直接报错找不到main public class String { public static void main(String[] args) { System.out.println("666"); } } // 执行结果 // Error: Main method not found in class java.lang.String, please define // the main method as: public static void main(String[] args) // or a JavaFX application class must extend javafx.application.Application

以上执行结果肯定是报错,因为运行程序肯定先执行main方法,要执行main方法就得先加载其类,也就是 java.lang.String(上面的自定义类) ,类加载请求来到类加载器,会一层一层往上委派,最终来到引导/启动类加载器(Bootstrap ClassLoader)。 然后该类加载器发现 java.lang.String 是核心API里面的类,是自己管理范畴(加载核心类库(JAVA_HOME/lib/rt.jar)),于是加载的是核心API里面的那个 java.lang.String ,但是这个 String 类里面是没有main方法的,于是就报错了。

2、父类委派机制带来什么好处

父类委派机制保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。

如图所示,自下而上查找某个类是否被加载过,首先从CustomClassLoader开始查找(findClass)如果有加载过直接返回,如果没加载过就会委派给AppClassLoader去查找,依次查询。

https://yd-note.oss-cn-beijing.aliyuncs.com/blog/JVM/Classloader/%E7%B1%BB%E7%9A%84%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6.png

https://yd-note.oss-cn-beijing.aliyuncs.com/blog/JVM/Classloader/%E7%88%B6%E7%B1%BB%E5%A7%94%E6%B4%BE.drawio.png

3、类的加载机制

  1. 装载:通过ClassLoader加载class文件字节码,生成Class对象
  2. 链接:把类的二进制数据合并到JRE中;
    • 校验:检查载入Class文件数据的正确性;
    • 准备:为类变量分配储存空间并设置类变量初始值;
    • 解析:JVM将常量池内的符号引用转成直接引用;
  3. 初始化:执行类变量赋值和静态代码块

4、双亲委派代码解析

一起看下在java.lang.ClassLoader中的loadClass方法,由递归实现

大体流程: 1. 子类先委托父类加载 2. 父类加载器有自己的加载范围,范围内没有找到,则不加载,并返回给子类 3. 子类在收到父类无法加载的时候,才会自己去加载

        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
// 使用指定的<a href="#name">二进制名称</a>加载类,默认实现按以下顺序搜索类 // 1. 调用findLoadedClass()检查类是否已经加载 // 2. 调用父类加载器方法的loadClass。 如果父级为null,则该类使用内置虚拟机的加载程序。 // 3. 调用父类加载器没找到则自己调用findClass(String)方法来找到类 // 如果使用上述步骤找到了该类,并且resolve标志为true,然后此方法将调用生成的Class 对象上的#resolveClass(Class)}方法。 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.(如果还是没有获得该类,调用findClass找到类) long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats (记录JVM数据) sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } // 链接到指定的类,初始化等 if (resolve) { resolveClass(c); } return c; } }

四、破坏双亲委派

自定义类加载器,重写 loadClass 方法、线程上下文类加载器

免责声明

本站提供Hack区的一切软件、教程和仅限用于学习和研究目的;不得将其用于商业或者非法用途,否则,一切后果由用户自己承担 您必须在下载后的24个小时之内, 从您的电脑中彻底删除。如果条件支持,请支持正版,得到更好的服务。 另如有侵权请邮件与我 联系处理。敬请谅解!

本文于   2021/12/23 下午  发布 

永久地址: https://madaoo.com/article/1474042154040037376

版权声明: 自由转载-署名-非商业性使用   |   Creative Commons BY-NC 3.0 CN