java的类加载器及其双亲委派机制
为什么要设计类加载器?
个人理解,仅供参考。java是跨平台的,运行的是字节码,每次运行时都是读取字节码然后执行字节码,类加载器就是把字节码读取进内存的一个工具,进入内存后才能有后续的步骤。而字节码的来源可以多种多样,可以是java代码编译出的一个.class文件,也可以是从网络或其他文件中读取出的二进制字节流。有了类加载器你就可以自己定义去哪里读取字节码。简而言之,没有类加载器,java字节码就无法进入内存。
双亲委派机制的“双亲”是指哪两个?
即当Java程序需要使用某个类时,负责加载这个类的类加载器会首先把加载请求委托给它的父加载器,而不是自己直接加载,这种机制被称为双亲委派机制。那么,这里的“双亲”又是指哪两个呢?
请看官方文档:https://docs.oracle.com/javase/tutorial/ext/basics/load.html
The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a "parent" class loader. When loading a class, a class loader first "delegates" the search for the class to its parent class loader before attempting to find the class itself.
翻译:Java 平台采用委派模型来加载类。其基本思想是,每个类加载器都有一个 “父” 类加载器。在加载一个类时,类加载器会先将查找该类的任务 “委派” 给它的父类加载器,然后才会尝试自己去查找这个类。
我也一直在困惑这个“双亲”到底是指谁?经过不断搜索,结合官方文档,首先可以确定——“双亲委派”这个翻译是不太准确的!表意不明!这里的“parent”和普通的class loader是没有继承关系的,不能算“亲”,但是鉴于毕竟是翻译,允许有点偏差,这一点我觉得还可以接受。其次,官方文档里没有提到“双”!不知道这个“双”是从何而来!非常令人迷惑!所以网上有人提到“双亲委派”是个错误翻译。
还有一种说法,也是我自己揣测过的,就是一个自定义类加载类时,会先委托给扩展类加载器,再由扩展类加载器委托给启动类加载器。这样就出现了“双亲”。图示如下
既然反正最终也都是要交给启动类加载器去加载,
那么多层加载器又有什么意义呢?
这就要老生常谈地说起加载器的分类:
1、启动类加载器。只加载java的核心类(<JAVA_HOME>\lib下的)。由于java核心类是所有程序运行的基础,一定要确保核心类的正常运行,所以把核心类单独划出来,交由专门的启动类加载器来加载,避免其他类来干扰。
2、扩展类加载器。负责加载<JAVA_HOME>\lib/ext下的类。一些自定义的类也可以放到该目录下交由扩展类加载器加载。
3、应用程序类加载器(又叫系统类加载器)。负责加载用户类路径上所指定的库。
这样分类加载的好处(来自百度)
避免类的重复加载:通过双亲委派机制,当一个类加载器收到类加载请求时,它会先将该请求委派给它的父类加载器去尝试加载。只有当父类加载器无法加载该类时,子类加载器才会尝试加载。这样可以确保同一个类只被加载一次,避免了类的重复加载,提高了加载效率。
保证类的一致性:由于父类加载器优先加载类,可以保证类的一致性,避免了不同类加载器加载同一个类的问题。这有助于维护系统的稳定性和一致性。
保护核心类库的安全性:JVM中的根类加载器负责加载核心类库,如java.lang包下的类。由于根类加载器是由JVM实现的,无法通过Java代码直接访问,这样可以防止恶意代码替换核心类库,保护系统的安全性。
提供扩展机制:双亲委派机制允许开发人员自定义类加载器,实现特定的加载策略,从而扩展Java的类加载机制。这种灵活性使得Java能够适应不同的应用场景和需求。
那么,又一个问题让我迷惑了:既然有双亲委派机制,而且扩展类加载器中也是有委派机制的,那么最终不就都会由启动类加载器来加载了吗?又这样分层还有什么意义?
实际,启动类加载器在判断到所加载的类不是核心类后会拒绝加载,最终扩展类还是由扩展类加载器加载的。
与java 1.2版本之前的旧代码的兼容
之前的自定义加载器都是重写loadClass()来加载类的,出现双亲委派机制后,为兼容旧代码,就又新增了一个方法findClass(),并改写了loadClass()使其符合双亲委派:有父加载器,使用父加载器加载,没有父加载器就使用启动类加载器加载,仍然失败就调用自己的findClass()加载。这样,如果原来的自定义加载器重写了loadClass()并且没有调用super.loadClass(),则是完全不受影响的。如果调用了super.loadClass()呢? 那就得改,引导用户按新规则来。