tomcat是异步非阻塞吗(浅谈Tomcat如何打破双亲委托机制)
tomcat是异步非阻塞吗
浅谈Tomcat如何打破双亲委托机制目录
- JVM的类加载器
- Tomcat的类加载器
- findClass
- loadClass
我们经常会遇到ClassNotFound异常,表明JVM在尝试加载某类时失败了。
要解决这个异常,你得知道
- 什么是类加载
- JVM如何加载类
- 为什么会出现ClassNotFound
想想Tomcat又是如何加载和管理Web应用下的Servlet呢?
Tomcat正是通过Context组件来加载管理Web应用的,所以今天我会详细分析Tomcat的类加载机制。但在这之前,我们有必要预习一下JVM的类加载机制,我会先回答一下一开始抛出来的问题,接着再谈谈Tomcat的类加载器如何打破Java的双亲委托机制。
Java的类加载,就是把字节码格式.class文件加载到JVM的方法区,并在JVM堆建立一个java.lang.Class对象实例,封装Java类相关的数据和方法。
Class对象是什么?
可以理解成业务类的模板,JVM根据该模板创建具体业务类对象实例。
JVM并非在启动时就把所有 .class 文件都加载一遍,而是程序在运行过程中用到该类才去加载。
JVM类加载由类加载器完成,JDK提供一个抽象类ClassLoader:
public abstract class ClassLoader { // 每个类加载器都有个父加载器 private final ClassLoader parent; public Class<?> loadClass(String name) { // 查找该类是否被加载过 Class<?> c = findLoadedClass(name); // 若未被加载过 if( c == null ){ // 【递归】委托给父加载器加载 if (parent != null) { c = parent.loadClass(name); } else { // 若父加载器为空,查找Bootstrap加载器是否加载过了 c = findBootstrapClassOrNull(name); } } // 若父加载器未加载成功,调用自己的findClass去加载 if (c == null) { c = findClass(name); } return c; } protected Class<?> findClass(String name){ // 1. 根据传入的类名name,到在特定目录下去寻找类文件,把.class文件读入内存 ... // 2. 调用defineClass将字节数组转成Class对象 return defineClass(buf, off, len); } // 将字节码数组解析成一个Class对象,用native方法实现 protected final Class<?> defineClass(byte[] b, int off, int len){ ... } }
JVM的类加载器是分层的父子关系,每个类加载器都持有一个parent字段指向父加载器。
- defineClass 工具方法:调用native方法把Java类的字节码解析成一个Class对象
- findClass 就是找到 .class 文件,可能来自文件系统或网络,找到后把 .class 文件读到内存得到字节码数组,然后调用defineClass方法得到Class对象
loadClass 首先检查这个类是不是已经被加载过了,如果加载过了直接返回,否则交给父加载器去加载。
这是个递归调用,即子加载器持有父加载器引用,当一个类加载器需加载一个Java类时,会先委托父加载器去加载,然后父加载器在自己加载路径中搜索Java类,当父加载器在自己的加载范围内找不到时,才会交还给子加载器加载,这就是双亲委托机制。
JDK的类加载器工作原理是一样的,区别只是加载路径不同,即findClass查找的路径不同。
双亲委托机制是为保证一个Java类在JVM的唯一性。假如你手滑写个与JRE核心类同名类,比如Object,双亲委托机制能保证加载的是JRE里的那个Object类,而不是你写的Object。
因为AppClassLoader在加载你的Object类时,会委托给ExtClassLoader去加载,而ExtClassLoader又会委托给BootstrapClassLoader,BootstrapClassLoader发现自己已经加载过了Object类,会直接返回,不会去加载你的Object类。
类加载器的父子关系不是通过继承来实现的,比如AppClassLoader并非ExtClassLoader的子类,只是AppClassLoader的parent指向ExtClassLoader对象。
所以若自定义类加载器,不是去继承AppClassLoader,而是继承ClassLoader抽象类,再重写findClass和loadClass即可。
Tomcat就是通过自定义类加载器实现自己的类加载。
若你要打破双亲委托,也就只需重写loadClass,因为loadClass的默认实现就是双亲委托机制。
Tomcat的自定义类加载器WebAppClassLoader打破了双亲委托机制:
首先自己尝试去加载某个类,如果找不到再委托给父类加载器,目的是优先加载Web应用自己定义的类。
只需重写ClassLoader的两个方法:
findClass
public Class<?> findClass(String name) throws ClassNotFoundException { ... Class<?> clazz = null; try { //1. 先在Web应用目录下查找类 clazz = findClassInternal(name); } catch (RuntimeException e) { throw e; } if (clazz == null) { try { //2. 如果在本地目录没有找到,交给父加载器去查找 clazz = super.findClass(name); } catch (RuntimeException e) { throw e; } //3. 如果父类也没找到,抛出ClassNotFoundException if (clazz == null) { throw new ClassNotFoundException(name); } return clazz; }
工作流程
- 先在Web应用本地目录下查找要加载的类
- 若未找到,交给父加载器查找,即AppClassLoader
- 若父加载器也没找到这个类,抛ClassNotFound
loadClass
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> clazz = null; //1. 先在本地cache查找该类是否已经加载过 clazz = findLoadedClass0(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } //2. 从系统类加载器的cache中查找是否加载过 clazz = findLoadedClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } // 3. 尝试用ExtClassLoader类加载器类加载,为什么? ClassLoader javaseLoader = getJavaseClassLoader(); try { clazz = javaseLoader.loadClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } } catch (ClassNotFoundException e) { // Ignore } // 4. 尝试在本地目录搜索class并加载 try { clazz = findClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } } catch (ClassNotFoundException e) { // Ignore } // 5. 尝试用系统类加载器(也就是AppClassLoader)来加载 try { clazz = Class.forName(name, false, parent); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } } catch (ClassNotFoundException e) { // Ignore } } //6. 上述过程都加载失败,抛出异常 throw new ClassNotFoundException(name); }
工作流程
- 先在本地Cache查找该类是否已加载过
- 即Tomcat的类加载器是否已经加载过这个类。
- 若Tomcat类加载器尚未加载过该类,再看看系统类加载器是否加载过
- 若都没有,就让ExtClassLoader加载,为防止Web应用自己的类覆盖JRE的核心类
- 因为Tomcat需打破双亲委托,假如Web应用里自定义了一个叫Object的类,若先加载该Object类,就会覆盖JRE的Object类,所以Tomcat类加载器优先尝试用ExtClassLoader去加载,因为ExtClassLoader会委托给BootstrapClassLoader去加载,BootstrapClassLoader发现自己已经加载了Object类,直接返回给Tomcat的类加载器,这样Tomcat的类加载器就不会去加载Web应用下的Object类了,避免覆盖JRE核心类。
- 若ExtClassLoader加载失败,即JRE无此类,则在本地Web应用目录下查找并加载
- 若本地目录下无此类,说明不是Web应用自己定义的类,那么由系统类加载器去加载。这里请你注意,Web应用是通过Class.forName调用交给系统类加载器的,因为Class.forName的默认加载器就是系统类加载器。
- 若上述加载过程都失败,抛ClassNotFound
可见 Tomcat 类加载器打破了双亲委托,没有一上来就直接委托给父加载器,而是先在本地目录下加载。
但为避免本地目录类覆盖JRE核心类,会先尝试用ExtClassLoader加载。
那为何不先用AppClassLoader加载?
若这样,就又变成双亲委托,这就是Tomcat类加载器的奥妙。
到此这篇关于浅谈Tomcat如何打破双亲委托机制的文章就介绍到这了,更多相关Tomcat 双亲委托机制内容请搜索开心学习网以前的文章或继续浏览下面的相关文章希望大家以后多多支持开心学习网!
- idea如何运行tomcat项目(在IDEA 2020.3.1中部署Tomcat并且创建第一个web项目的过程详解)
- idea如何搭建tomcat(IDEA2020.1.2创建web项目配置Tomcat的详细教程)
- maven默认插件配置(Maven使用tomcat8-maven-plugin插件的详细教程)
- tomcat环境配置教程(Tomcat服务器的安装配置图文教程推荐)
- tomcat在linux系统安装步骤(Tomcat的卸载和重装的实现图文)
- tomcat与eclipse配置运行(Tomcat配置及如何在Eclipse中启动)
- 怎么把jar包部署到tomcat(使用tomcat设定shared lib共享同样的jar)
- 如何认识服务器(关于Nginx、Apache、Tomcat三个WEB服务器的区别和认知)
- apache tomcat安装教程(Apache及Tomcat搭建集群环境过程解析)
- Tomcat和Weblogic部署纯html文件过程解析(Tomcat和Weblogic部署纯html文件过程解析)
- linux部署tomcat项目(阿里云服务器linux系统搭建Tomcat部署Web项目)
- 如何使用docker启动tomcat(Docker容器上用DockerFile部署多个tomcat服务的步骤)
- tomcat作用及原理(详解Tomcat常用的过滤器)
- tomcat运行警告(tomcat异常解决Invalid character found in the request target. The valid characters are defined in)
- apache连接tomcat配置(Apache结合Tomcat实现动静分离的方法)
- nginx tomcat docker 负载均衡(Nginx+Tomcat实现负载均衡、动静分离的原理解析)
- 《无敌破坏王2》 不聊彩蛋,聊聊我从动画里看到的现实那些事儿(无敌破坏王2不聊彩蛋)
- 《寄生虫》 三观不正 人类悲欢从来不相通,感同身受也并非本能(寄生虫三观不正)
- 这部动漫中的女孩子,可比101女孩更加励志(这部动漫中的女孩子)
- 《白狐的人生》热拍 贾征宇偶像包袱难自弃 图(白狐的人生热拍)
- 七夕取消了,牛郎织女没做核酸七夕已经取消(牛郎织女没做核酸七夕已经取消)
- 网友抵制 多地取消 夏日祭 为何惹众怒(网友抵制多地取消)
热门推荐
- navicat中如何使用sql语句(Navicat Premium操作MySQL数据库执行sql语句)
- html5播放视频的元素(HTML5在手机端实现视频全屏展示方法)
- mysql未使用索引的查询(如何在mysql进行查询缓存及失败的解决方法)
- linux如何搭建ftp服务器(Linux下搭建ftp服务器)
- ASP.NET参数化查询
- javascript 经典算法(JavaScript实现的七种排序算法总结推荐!)
- SQLServer数据库从高版本降级到低版本实例详解(SQLServer数据库从高版本降级到低版本实例详解)
- mysql触发器怎么添加(MySQL触发器的使用场景及方法实例)
- linux常用的参数类型和参数代码(浅析Linux resolv.conf)
- harbor 1.8安装部署(docker5 全功能harbor仓库搭建过程)
排行榜
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9