做期货主要看哪几个财经网站,大美工设计网站官网,网站如何备案icp,域名的正确书写格式目录
一、JVM 类加载机制
二、Tomcat 类加载器 2.2 findClass 介绍 3.2 loadClass 介绍
三、web应用隔离 3.1 Spring 加载问题 在开始文章内容之前#xff0c;先来看三个问题
假如在 Tomcat 上运行了两个 Web 应用程序#xff0c;两个 web 应用中有同名的Servlet#xf…目录
一、JVM 类加载机制
二、Tomcat 类加载器 2.2 findClass 介绍 3.2 loadClass 介绍
三、web应用隔离 3.1 Spring 加载问题 在开始文章内容之前先来看三个问题
假如在 Tomcat 上运行了两个 Web 应用程序两个 web 应用中有同名的Servlet比如都叫UserController但是功能不同Tomcat 需要同时加载和管理这两个同名的 Servlet 类保证他们不会冲突那怎么才能实现隔离假如两个 web 应用都依赖同一个第三方 jar 包比如spring那 spring 的 jar 包被加载到内存后Tomcat 保证这两个 web 应用能共享也就是说 spring 的 jar 包只被加载一次否则随着依赖第三方的增多JVM的内存会爆炸这时怎么做到的跟JVM一样怎样隔离 Tomcat 本身和 web 应用类 以上三个问题本文会逐一来讲解下面先来看下 JVM 的类加载机制。
一、JVM 类加载机制 Java 的类加载就是把字节码格式 “.class” 文件加载到 JVM 方法区并在 JVM 的堆中建立一个 java.lang.class 对象实例用来封装 Java 类相关的数据和方法。 JVM 并不是在启动时就把所有的 “.class” 文件都加载一遍而是程序在运行过程中用到了这个类才去加载。JVM 类加载是由类加载器来完成的JDK 提供一个抽象类 ClassLoader这个抽象类中定义了三个关键方法理解清楚他们的作用和关系非常重要。
JVM 的类加载器是有层次结构的他们有父子关系每个类加载器都有一个 parent 字段指向父类加载器defineClass 是个工具方法它的职责是调用 native 方法把 Java 类的字节码解析成一个Class 对象所谓的 native 方法就是由C语言实现的方法Java通过 JNI 机制调用findClass 方法的主要职责就是找到 “.class” 文件可能来自文件系统或者网络找到后把“.class” 文件读到内存得到字节码然后调用 defineClass 方法获得 Class 对象loadClass 是个 public 方法说明它才是对外提供服务的接口具体实现也比较清晰首先检查这个类是不是已经被加载过了如果加载过了直接返回否则交给父加载器去加载。注意这是一个递归调用也就是说子加载器持有父加载器的引用当一个类加载器需要加载一个 Java 类时会先委托父加载器去加载然后父加载器在自己的加载路径中搜索 Java 类当父加载器在自己的加载范围内找不到时才会交还给子加载器加载这就是双亲委托机制。
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 双亲委派如图 BootstrapClassLoader 是启动类加载器由 C 语言实现用来加载 JVM 启动时所需要的核心类比如 rt.jar、resource.jar 等。ExtClassLoader 是扩展类加载器用来接在 \jre\lib\ext 目录下的 jar 包。AppClassLoader 是系统类加载器用来加载 classpath 下的类应用程序默认用它来加载类。自定义类加载器用来加载自定义路径下的类。 这些类加载器的工作原理是一样的区别是他们的加载路径不同也就是说 findClass 这个方法查找的路径不同。双亲委派机制是为了保证一个 Java 类在 JVM 中是唯一的假如不小心写了一个与 JRE 核心类同名的类比如 Object 类双亲委派机制能保证加载的是 JRE 里的那个Object 类而不是你自己写的 Object 类。这是因为 AppClassLoader 在加载你的 Object 类时会委托给 ExtClassLoader 去加载而 ExtClassLoader 又会委托给 BootstrapClassLoaderBootstrapClassLoader 发现自己已经加载过了 Object 类会直接返回不会去加载你写的 Object 类。 注意类加载器的父子关系不是通过继承来实现的比如 AppClassLoader 并不是 ExtClassLoader 的子类而是说 AppClassLoader 的 parent 成员变量指向 ExtClassLoader 对象。
二、Tomcat 类加载器 Tomca t的自定义类加载器 WebAppClassLoader 打破了双亲委派机制首先自己尝试去加载某个类如果找不到再代理给父类加载器其目的是优先加载 Web 应用自己定义的类。具体实现就是重写 ClassLoader 的两个方法findClass 和 loadClass。看下下面的源码 2.2 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. 如果父类也没找到抛出ClassNotFoundExceptionif (clazz null) {throw new ClassNotFoundException(name);}return clazz;
} 在 findClass 方法里主要有三个步骤
先在 Web 应用本地目录下查找要加载的类。如果没有找到交给父加载器去查找它的父加载器就是上面提到的系统类加载器 AppClassLoader。如果父加载器也没找到这个类抛出 ClassNotFound 异常。 3.2 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);
} loadClass 方法稍微复杂一点主要有六个步骤
先在本地 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 的核心类先尝试用 JVM 扩展类加载器 ExtClassLoader 去加载。
三、web应用隔离 先回答开头提到的第一个问题如果我们使用 JVM 的 AppClassLoader 来加载 web 应用AppClassLoader 只能加载一个 Servlet再加载第二个同名的 Servlet 时会返回第一个加载的 Servlet同名的只被加载一次。Tomcat的解决方案是自定义一个类加载器 WebAppClassLoader,并且给每个 web 应用创建一个类加载器。不同的类加载器加载的类被认为是不同的类即使名称相同web 应用通过各自的类加载器来实现隔离。 在来看第二个问题多个 web 应用之间需要共享类库并且不能重复加载相同的类。在双亲委派机制里各个子加载器都可以通过父加载器去加载类那么把需要共享的类放到父加载器的加载路径下不就行了吗应用程序也是通过这种方式共享 JRE 核心类。因此 Tomcat 的设计者又加了一个类加载器 SharedClassLoader作为 WebAppClassLoader 的父加载器专门来加载web 应用之间共享的类。如果 WebAppClassLoader 自己没有加载到某个类就会委托父加载器SharedClassLoader 去加载这个类SharedClassLoader 会在指定目录下加载共享类之后返回给 WebAppClassLoader这样共享的问题就结局了。 第三个问题如何隔离 Tomcat 本身的类和 web 应用的类要共享可以通过父子关系要隔离那就需要兄弟关系了。兄弟关系就是指两个类加载器是平行的他们可能拥有同一个父加载器但是两个兄弟类加载器加载的类是隔离的。因此 Tomcat 又设计了一个类加载器CatalinaClassLoader专门来加载 Tomcat 自身的类这样设计有个问题那 Tomcat 和 web 需要共享一些类时怎么办呢 还是再增加一个 CommonClassLoader作为 CatalinaClassLoader 和 SharedClassLoader 的父加载器。CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用而 CatalinaClassLoader 和 SharedClassLoader 能加载的类则与对方相互隔离。WebAppClassLoader 可以使用 SharedClassLoader 加载到的类但各个 WebAppClassLoader 实例之间相互隔离。 3.1 Spring 加载问题 在 JVM 的实现中有一条规则如果一个类由类加载器 A 加载那么这个类的依赖类也由相同的类加载器完成。Spring 作为一个 bean 工厂需要创建业务实体类并且在创业业务类之前只要创建依赖类。 前面提到web 应用之间共享的 JAR 包可以交给 SharedClassLoader 来加载从而避免了重复Spring 作为共享的三方 JAR 包它自己是由 SharedClassLoader 加载的但是Spring又要去加载业务类但是业务类不在 SharedClassLoader 对应的目录下那该怎么办呢 Tomcat 使用了线程上下文加载器它其实是一种类加载传递机制。这个类加载器保存在线程私有数据里只要是同一个线程一旦设置了线程上下文加载器在线程后续执行过程中就能把这个加载器取出来用。 Tomcat 为每个 Web 应用创建一个 WebAppClassLoader 类加载器并在启动 Web 应用的线程里设置线程上下文加载器这样 Spring 在启动时就将线程上下文加载器取出来用来加载 Bean。这样就完成了 SharedClassLoader 创建的 Spring 可以创建 WebAppClassLoader 下的业务类是不是设计的很精妙呢 好了本期内容就介绍到这里。 往期经典推荐
Tomcat架构究竟是什么灵魂原来在这里-CSDN博客
你真的了解Tomcat一键启停吗-CSDN博客
你所不知的Tomcat网络通信的玄机-CSDN博客
决胜高并发战场Redis并发访问控制与实战解析-CSDN博客
TiDB内核解密揭秘其底层KV存储引擎如何玩转键值对-CSDN博客