说一说JVM双亲委派机制与Tomcat

https://www.cnblogs.com/dengchengchao/p/11844022.html

讲个故事:

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

package java.lang;

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

    public boolean equals(Object anObject) {
        sendEmail(xxx);
        return equalsReal(anObject);
    }

    //...
}

这样,只要引用java.lang.String的人,小明能随时收到他的系统的相关信息,这简直是个天才的注意。然而实施的时候却发现,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中的ClassLoader详解

Java类加载机制与Tomcat类加载器架构

【JVM】浅谈双亲委派和破坏双亲委派

关于Java类加载双亲委派机制的思考

[转帖]说一说JVM双亲委派机制与Tomcat的更多相关文章

  1. 说一说JVM双亲委派机制与Tomcat

    双亲委派模型与JVM 类加载 讲个故事: 以前,爱捣鼓的小明突然灵机一动,写出了下面的代码 package java.lang; public class String { //...复制真正Stri ...

  2. jvm双亲委派机制详解

    双亲委派机制 ​ 记录一下JVM的双亲委派机制学习记录. 类加载器种类 ​ 当我们运行某一个java类的main方法时,首先需要由java虚拟机的类加载器将我们要执行的main方法所在的class文件 ...

  3. JVM加载类的过程,双亲委派机制中的方法

    JVM加载类的过程: 1)JVM中类的整个生命周期: 加载=>验证=>准备=>解析=>初始化=>使用=>卸载  1.1.加载 类的加载阶段,主要是获取定义此类的二进 ...

  4. 深入JVM系列(三)之类加载、类加载器、双亲委派机制与常见问题

    一.概述   定义:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型.类加载和连接的过程都是在运行期间完成的. 二. 类的 ...

  5. JVM学习六:JVM之类加载器之双亲委派机制

    前面我们知道类加载有系统自带的3种加载器,也有自定义的加载器,那么这些加载器之间的关系是什么,已经在加载类的时候,谁去加载呢?这节,我们将进行讲解. 一.双亲委派机制 JVM的ClassLoader采 ...

  6. JVM之类加载器、加载过程及双亲委派机制

    JVM 的生命周期 虚拟机的启动 Java 虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实 ...

  7. JVM类加载与双亲委派机制被打破

    前言 前文已经讲了虚拟机将java文件编译成class文件后的格式:JVM虚拟机Class类文件研究分析 java文件经过编译,形成class文件,那么虚拟机如何将这些Class文件读取到内存中呢? ...

  8. JVM双亲委派模型及其优点

    JVM双亲委派模型及其优点 什么是双亲委派模型? 双亲委派模型: ​ 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器, ...

  9. JVM探究(一)谈谈双亲委派机制和沙箱安全机制

    JVM探究 请你谈谈你对JVM的理解?java8虚拟机和之前的变化gengxin? 什么是OOM,什么是栈溢出StackOverFlowError JVM的常用调优参数有哪些? 内存快转如何抓取,怎么 ...

随机推荐

  1. maven 使用dependencyManagement统一管理依赖版本

    今日思语:人生方方长长,努力把她磨成方圆,所以 加油咯~ 使用maven可以很方便的进行项目依赖的管理,即可以管理我们显示引入具体版本的依赖,也可以管理某些第三方引入的一些依赖的版本,从而能更好的实现 ...

  2. 实现:调用API函数ShowWindow()来隐藏窗口

    只需要将相应代码复制即可. 代码如下: #include <iostream> #include <windows.h> int main() { HWND hDos; //声 ...

  3. volatile 关键词

    volatile 关键字指示一个字段可以由多个同时执行的线程修改. 出于性能原因,编译器,运行时系统甚至硬件都可能重新排列对存储器位置的读取和写入. 声明了 volatile 的字段不进行这些优化.这 ...

  4. Lightning Web Components 组件生命周期(六)

    组件创建以及渲染流程 组件移除dom 处理流程 组件从dom 移除 组件中的disconnectedCallback() 方法被调用 子组件从dom 移除 每个子组件的disconnectedCall ...

  5. Mac OSX上安装SublimeText 3编译Processing 3.0

    1.安装SublimeText 32.使用Preferences - Package Control:Install Package - Processing安装 如果移动了Processing的位置 ...

  6. Android Studio一直显示Building“project name”Gradle project info问题详解

    关注我,每天都有优质技术文章推送,工作,学习累了的时候放松一下自己. 本篇文章同步微信公众号  欢迎大家关注我的微信公众号:「醉翁猫咪」 Android Studio一直显示 Building&quo ...

  7. 平安银行Java面试-社招-五面(2019/09)

    个人情况 2017年毕业,普通本科,计算机科学与技术专业,毕业后在一个二三线小城市从事Java开发,2年Java开发经验.做过分布式开发,没有高并发的处理经验,平时做To G的项目居多.写下面经是希望 ...

  8. web 视频播放器clappr 相关

    https://github.com/tjenkinson/clappr-thumbnails-plugin/ https://github.com/andrefilimono/clappr-flvj ...

  9. 全面解读Group Normalization,对比BN,LN,IN

    前言 Face book AI research(FAIR)吴育昕-何恺明联合推出重磅新作Group Normalization(GN),提出使用Group Normalization 替代深度学习里 ...

  10. 开源一个golang小程序商城后台系统(moshopserver)

    开源一个golang小程序商城后台(moshopserver) golang和c/c++比起来是一门新的语言,一直想学,网上搜集了一些资料,有些人说很容易上手,确实是这样,和C/C++比起来,少了很多 ...