说一说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.jarBootstrap 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. webpack的loader的原理和实现

    想要实现一个loader,需要首先了解loader的基本原理和用法. 1. 使用 loader是处理模块的解析器. module: { rules: [ { test: /\.css$/, use: ...

  2. BZOJ 3451: Tyvj1953 Normal 点分治+FFT

    根据期望的线性性,我们算出每个点期望被计算次数,然后进行累加. 考虑点 $x$ 对点 $y$ 产生了贡献,那么说明 $(x,y)$ 之间的点中 $x$ 是第一个被删除的. 这个期望就是 $\frac{ ...

  3. BZOJ 1213: [HNOI2004]高精度开根

    二次联通门 : BZOJ 1213: [HNOI2004]高精度开根 正解 NTT+高精+倍增+二分 但是可以用python 2333333 m,n=int(raw_input()),int(raw_ ...

  4. 1、zookeeper入门

    一.什么是Zookeeper Zookeeper是Google的Chubby一个开源的实现,是一个开源的,为分布式提供协调服务的Apache项目; 它包含一个简单的原语集,分布式应用程序可以基于它实现 ...

  5. 指针的运算符&、*

    int y=0; int* yptr=&y; •互相反作用 •*&yptr -> * (&yptr) -> * (yptr的地址)-> 得到那个地址上的变量 ...

  6. Echarts 入门操作

    Echarts具有丰富的图表,可以说是数据可视化的神器: 1.下载Echarts 到官网或者点击以下文字[下载Echarts]即可下载: ①官网下载地址:https://echarts.baidu.c ...

  7. 对5月13号中BaseDao方法进行优化改造,更接近于框架的编写

    /* * 通用查询.更新升级版 * */ public class BaseDao2 { static { try { Class.forName(ConfigUtil.getValue(" ...

  8. CSS各类布局

    http://www.liquidapsive.com/ 1 静态布局(Static Layout)网页上的所有元素的尺寸一律使用px作为单位.特点:不管浏览器尺寸具体是多少,网页布局始终按照最初写代 ...

  9. #C++初学记录(算法效率与度量)

    时间性能 算法复杂性函数: \[ f(n)=n^2 +1000n+\log_{10}n+1000 \] 当n的数据规模逐渐增大时,f(n)的增长趋势: 当n增大到一定值以后,计算公式中影响最大的就是n ...

  10. 以SQL命令方式调用存储过程

    string str = "Data Source=.;Initial Catalog=***;Integrated Security=True"; using (SqlConne ...