双亲委派模型与JVM 类加载

讲个故事:

以前,爱捣鼓的小明突然灵机一动,写出了下面的代码

package java.lang;

/**

* @author dengchengchao

* @date 2019/2/26 19:20

*/

public class String {

//...复制真正String的其他方法

public boolean equals(Object anObject) {

sendEmail(xxx);

return equalsReal(anObject);

}

//...

}

这样,只要引用这个包的人,小明能随时收到他的系统的相关信息,这简直是个天才的注意。然而实施的时候却发现,JVM并没有加载这个类。

这是为什么呢?

小明能想到的事情,JVM设计者也肯定能想到。

双亲委派模型

上述故事纯属瞎编,不过,这确实是以前JVM存在的一个问题,这几天看Tomcat源代码的时候,发现频繁出现ClassLoader为什么要用这个东西呢?

想要解答这个问题,得先了解一个定义:双亲委派模型。

这个词第一次看见是在《深入理解JVM》中,目的也是为了解决上面所提出来的问题。

在JVM中,存在三种类型的类加载器:

启动类(Bootstrap)加载器:用于加载本地(Navicat)代码类的加载器,它负责装入%JAVA_HOME%/lib下面的类。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

标准扩展(Extension)类加载器:由ExtClassLoader实现,负责加载%JAVA_HOME/lib/ext%或者系统变量java.ext.dir(可使用System.out.println("java.ext.dir")查看)指定的类加载到内存中

系统(System)类加载器:由AppClassLoader实现,负责加载系统类(环境变量%CLASSPATH%)指定,默认为当前路径的类加载到内存中。

除去以上三种外,还有一种比较特殊的线程上下文类加载器。存在于Thread类中,一般使用方式为new Thread().getContextClassLoader()

可以看出来,三种类型的加载器负责不同的模块的加载。那怎么才能保证我所使用的String就是JDK里面的String呢?这就是双亲委派模型的功能了:

上面三种类加载器中,他们之间的关系为:

也就是Bootstrap ClassLoader作为Extension ClassLoader的父类,而Extension ClassLoader作为Application ClassLoader的父类,Application ClassLoader是作为User ClassLoader的父类的。

而双亲委派机制规定:当某个特定的类加载在接收到类加载的请求的时候,首先需要将加载任务委托给父类加载器,依次递归到顶层后,如果最高层父类能够找到需要加载的类,则成功返回,若父类无法找到相关的类,则依次传递给子类。

补充:

如果A类引用了B,则JVM将使用加载类A的加载器加载类B

类加载器存在缓存,如果某个加载器以前成功加载过某个类后,再次接受到此类加载请求则直接返回,不再向上传递加载请求

可以通过ClassLoader.loadClass()或Class.ForName(xxx,true,classLoader)指定某个加载器加载类

类类型由加载它的加载器和这个类本身共同决定,如果类加载器不同,类名相同,instanceof依然会返回false

父加载器无法加载子加载器能够加载的类

可以看到,通过双亲委派机制,能够保证使用的类的安全性,并且可以避免类重名的情况下JVM存在多个相同的类名相同,字节码不同的类。

回到刚开始讲的故事,虽然小明自定义了String,包名也叫java.lang,但是当用户使用String的时候,会由普通的Application ClassLoader加载java.lang.String,此时通过双亲委派,类加载请求会上传给Application ClassLoader的父类,直到传递给Bootstrap ClassLoader,而此时,Bootstrap ClassLoader将在%JAVA_HOME%/lib中寻找java.lang.String而此时正好能够找到java.lang.String,加载成功,返回。因此小明自己写的java.lang.String并没有被加载。

可以看见,如果真的想要实现小明的计划,只能将小明自己编写的java.lang.String这个class文件替换到%JAVA_HOME%/lib/rt.jar 中的String.class

自定义ClassLoader

到这里,估计能明白为什么需要双亲委派模型了,而某些时候,我们可以看见许多框架都自定义了ClassLoader,通过自定义ClassLoader,我们可以做很多好玩的事情,比如:设计一个从指定路径动态加载类的类加载器:

public class DiskClassLoader extends ClassLoader {

private String libPath;

public DiskClassLoader(String path){

libPath=path;

}

@Override

protected Class> findClass(String name) throws ClassNotFoundException {

try(FileInputStream fileInputStream=new FileInputStream(new File(libPath,getFileName(name)));

BufferedInputStream bufferedInputStream=new BufferedInputStream(fileInputStream);

ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream()){

for (int len=0;(len=bufferedInputStream.read())!=-1;){

byteArrayOutputStream.write(len);

}

byte[] data=byteArrayOutputStream.toByteArray();

return defineClass(name,data,0,data.length);

}catch (IOException e){

e.printStackTrace();

}

return super.findClass(name);

}

private String getFileName(String name) {

int index = name.lastIndexOf('.');

if(index == -1){

return name+".class";

}else{

return name.substring(index+1)+".class";

}

}

}

上面是一个简单的例子,可以看见想要自定义ClassLoader,只需要继承ClassLoader,然后覆盖findClass()方法即可,其中findClass()是负责获取指定类的字节码的,在获取到字节码后,需要手动调用defineClass()加载类。

在ClassLoader类中,我们能找到loadClass的源代码:

protected Class> loadClass(String name, boolean resolve) {

// First, check if the class has already been loaded

Class> c = findLoadedClass(name);

if (c == null) {

if (parent != null) {

c = parent.loadClass(name, false);

} else {

c = findBootstrapClassOrNull(name);

}

if (c == null) {

c = findClass(name);

}

}

if (resolve) {

resolveClass(c);

}

return c;

}

在删减掉一些模板代码后,我们可以看到loadClass()方法就是实现双亲委派的主要代码:首先查看类是否有缓存,如果没有,就调用父类的loadClass方法,让父类去加载,如果父类加载失败,则自己加载,如果自己加载失败,那就返回null,注意:并没有再找自己的子类去寻找类,也就是在哪里发起的加载,就在哪里结束。

这里可以看到,loadClass()方法并没有被标记为final的,也就是我们依然可以重载它的loadClass()方法,破坏原本的委派双亲模型。

破坏双亲委派机制

有些时候,双亲委派机制也会遇到一些问题,在介绍双亲委派机制的时候,我列举了一些补充。而在一些JDK中,存在一些基础API他们的加载由比较上层的加载器负责,这些API只是一些简单的接口,而具体的实现可能会由其他用户自己实现,这个时候就存在一个问题,如果这些基础的API需要调用/加载用户的代码的时候,会发现由于父类无法找到子类所能加载的类的原因,调用失败。

最典型的例子便是JNDI服务,JNDI服务是在JDK1.3的时候放入rt.jar中,而rt.jar有Bootstrap ClassLoader加载,JNDI的功能是对资源进行集中管理和查找,它需要调用独立厂商实现部部署在应用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代码,但启动类加载器不可能“认识”之些代码,该怎么办?

这就需要用到最开始讲的特殊的加载器:上下文类加载器

上下文类加载器的使用方式为:Thread.currentThread().getContextClassLoader()

上下文类加载器是什么意思呢?可以看源码,Thread初始化是通过本地方法currentThread();初始化的,而classLoader也正是通过currentThread初始化,currentThread指的是当前正在运行的线程。

而默认情况下,启动Launcher后,Launcher会将当前线程的上下文加载器设置为Application ClassLoader

public Launcher() {

...

try {

this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);

} catch (IOException var9) {

throw new InternalError("Could not create application class loader", var9);

}

Thread.currentThread().setContextClassLoader(this.loader);

...

}

因此,上下文类加载器默认就是系统加载器,通过上下文加载器,更高级别的加载器便可以调用系统加载器加载一个类。

Tomcat 与类加载器

Tomcat作为一个Web容器,会包含各种Web应用程序,而为了使各个应用程序不互相干扰,至少需要达到以下要求:

部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以实现相互隔离

部署在同一个Web容器上的两个Web应用程序所使用的Java类库可以相互共享

Web容器需要保证自身的安全不受Web应用程序所影响

只是JSP的容器,需要支持热部署功能

因为这些需求,所以在Tomcat中,类的加载不能使用简单的ClassLoader来加载,而是需要自定义分级的ClassLoader。

在Tomcat中,定义了3组目录结构/common/*,/server/*和/shared/*可以存放Java类库,另外还有Web应用程序自身的结构:/WEB-INF/*,而这几级目录结构分别对应了不同的加载器

common:类库可以被Tomcat和所有Web应用程序共同使用

server:类库可以被Tomcat使用,对其他Web程序不可见

shared:类库可以被所有的Web应用程序共同使用,但对Tomcat不可见

WEB-INF:类库仅仅能被自身Web应用程序使用

因此,需要支持以上结构,可以通过自定义遵循双亲委派模型的ClassLoader来完成。

参考链接:

java 双亲委派_JVM双亲委派机制与Tomcat相关推荐

  1. java类加载器、双亲委派、沙箱安全机制全都让你整明白(三万字,收藏慢慢啃)

    目录 一.概述 1.类加载的分类 2.类加载器的必要性 3.命名空间 4.类加载机制的基本特征 二.类加载器的分类 1.引导类加载器 2.扩展类加载器 3.系统类加载器 4.用户自定义类加载器 三.测 ...

  2. java类加载过程(双亲委派机制)

    类加载运行全过程 通过Java命令执行代码的大体流程如下: 其中loadClass的类加载过程有如下几步: 加载 >> 验证 >> 准备 >> 解析 >> ...

  3. java类加载过程,双亲委派机制

    1.双亲委派机制 Parent Delegation Model 又称为父级委托模型.想要了解它,还需理解类加载机制.类加载器.类加载器的层级关系. 2.类加载机制: 编译器把Java源文件编译成.c ...

  4. Android插件化开发基础之Java类加载器与双亲委派模型

    类加载器 Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程. 在加载阶段,java虚拟机需 ...

  5. Java中的ClassLoader和SPI机制

    深入探讨 Java 类加载器 成富是著名的Java专家,在IBM技术网站发表很多Java好文,也有著作. 线程上下文类加载器 线程上下文类加载器(context class loader)是从 JDK ...

  6. Java虚拟机 —— 类的加载机制

    我们知道class文件中存储了类的描述信息和各种细节的数据,在运行Java程序时,虚拟机需要先将类的这些数据加载到内存中,并经过校验.转换.解析和初始化过后,最终形成可以直接使用的Java类型. 类从 ...

  7. 【Java】反射( reflection)机制 详解

    目录 1. 定义 2. 用途(了解即可) 3. 反射的基本信息 4. 反射相关的类(重要) 4.1 Class类(反射机制的起源 ) 4.2 Class类中的相关方法(方法的使用方法在后边的示例当中) ...

  8. 完成这个例子,说出java中针对异常的处理机制。

    有一个类为ClassA,有一个类为ClassB,在ClassB中有一个方法b,此方法抛出异常,在ClassA类中有一个方法a,请在这个方法中调用b,然后抛出异常.在客户端有一个类为TestC,有一个方 ...

  9. 【Java 并发编程】线程池机制 ( ThreadPoolExecutor 线程池构造参数分析 | 核心线程数 | 最大线程数 | 非核心线程存活时间 | 任务阻塞队列 )

    文章目录 前言 一.ThreadPoolExecutor 构造参数 二.newCachedThreadPool 参数分析 三.newFixedThreadPool 参数分析 四.newSingleTh ...

最新文章

  1. Android开发之Service通过Messenger实现线程间的通信
  2. .NET MVC Filter异常处理
  3. dva 打包多个html,使用dva+umi+antd构建页面(一)
  4. ASP.NET-get与post模式的区别
  5. 七、MyBatis教程之四多表关系的实现
  6. html整体引入js,html页面用js引入js的方式
  7. ABP理论学习之日志记录
  8. nginx的root和alias指令的区别
  9. signature=12e3283d637b587235bcb4cbbfa1a5b3,A pathogen-inducible endogenous siRNA in plant immunity
  10. (CVPR2019)图像语义分割(17)-DFANet:用于实时语义分割的深层特征聚合网络
  11. 一些有用的书签网站整理
  12. Windows下【AxureRP】原型设计工具破解码与安装包
  13. arcgis中将地理坐标转换为投影坐标 / 经纬度坐标转换
  14. python数据库进阶
  15. JavaScript随手笔记之--html拼接语句传递json数据
  16. 跟光磊学Java-macOS版Java8开发环境搭建(基于ARM 64-bit)
  17. 金属结构保温板的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  18. Android 性能优化五大误区和两大疑点!
  19. 2018最新引流脚本话术设置,引流话术大全集合
  20. Window10 JDK8安装与配置详细步骤

热门文章

  1. IDEA导入外部Module步骤
  2. 利用gantt-elastic 实现简易项目甘特图
  3. 如何安全升级 TiDB
  4. 蒙氏计算机教育目的,蒙氏数学领域基础理论及教育目的
  5. 一个按钮控制灯的开关
  6. MySQL-修改数据
  7. kotlin之字符串
  8. linux访问共享命令,Linux访问共享资源命令 smbclient
  9. tis红石生存服务器后勤系统,我的世界红石生存服务器TIS,国内大神团队制作,让全世界震撼...
  10. SaaS化MR直播正式上线!保利威开启轻量化虚拟直播时代