一、Java & Tomcat 类加载机制


一、Java 类加载过程

  1. 将多个 java文件,经过 编译打包 生成可运行的 jar包。
  2. 首先需要通过 类加载器 把 主类 加载到 JVM。
  3. 最终由 java命令,运行主类的 main()函数,启动程序。
  4. 主类在运行过程中,如果使用到其他类,会逐步加载这些类。

  • 注意:jar 包里的类 不是一次性全部加载的,而是 使用到时才加载

1. Java 类加载过程分七步

1加载 > 2验证 > 3准备 > 4解析 > 5初始化 > 6使用 > 7卸载


在这里插入图片描述

  1. 加载:
    在硬盘上查找,并通过 IO 读入字节码文件
    使用到类时才会加载。
    例如:调用类的 main()方法,new对象 等等。
  2. 验证:
    校验 字节码文件 的正确性。
  3. 准备:
    给类的 静态变量 分配内存,并赋予默认值。
  4. 解析:
    符号引用 替换为 直接引用
    该阶段会把一些 静态方法(比如:main()方法),替换为指向数据所在内存的 指针 或 句柄 等(直接引用)。
  1. 这是所谓的 静态链接 过程(类加载期间完成)。
  2. 动态链接 是在程序运行期间完成的,将 符号引用 替换为 直接引用。
  1. 初始化:
    对类的 静态变量 初始化为指定的值,执行静态代码块。

二、Java 类加载器

  • 上面 Java 类加载过程,主要是通过 Java 类加载器 来实现的。

1. Java 三个核心类加载器

  1. 启动类加载器:
    负责加载支撑 JVM 运行的,位于 JRE 的 lib 目录下的 核心类库
    比如:rt.jar、charsets.jar 等。
  2. 扩展类加载器:
    负责加载支撑 JVM 运行的,位于 JRE 的 lib/ext 目录下的 扩展 jar 包
  3. 应用程序类加载器:
    负责加载 ClassPath 路径下的类包,主要就是加载自己写的类。
  4. 自定义加载器:
    负责加载用户 自定义路径 下的类包。

2. 自定义类加载器

  • 自定义类加载器:
  1. 继承 java.lang.ClassLoader 类。
  2. 重写 loadClass(..) 方法,实现了 双亲委派机制
  3. 重写 findClass(.) 方法。

  • loadClass(..) 方法:实现了 双亲委派机制
  1. 首先检查一下 指定类名 是否已经加载过。
    如果加载过了就不需要再加载,直接返回。
  2. 如果没有加载过,再判断一下是否有 父加载器
    如果有父加载器,则由父加载器加载(即:调用 parent.loadClass(name, false);)。
    否则调用 BootstrapClassLoader 来加载。
  3. 如果 父加载器 及 BootstrapClassLoader,都没有找到指定的类。
    那么调用 当前类加载器 的 findClass(.) 方法来完成类加载。

  • findClass(.) 方法:默认的实现是抛出 ClassNotFoundException 异常。
    所以 自定义类加载器 必须重写这个方法。

/**
 * @author wy
 * describe 自定义类加载器
 */
public class MyClassLoader extends ClassLoader {

	@Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        return super.loadClass(name, resolve);
    }

	@Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        return super.findClass(className);
	}
}	

3. 双亲委派机制

  • 加载某个类时,会先委托 父加载器 寻找目标类,找不到再委托 上层父加载器 加载。
  • 如果所有 父加载器 都找不到目标类,则在 自己的类加载路径 下查找并载入目标类。
    在这里插入图片描述

  • 加载 MyClass 类:
  1. 最先会找 应用程序类加载器 加载,应用程序类加载器 会委托 扩展类加载器 加载,扩展类加载器 再委托 启动类加载器 加载。
  2. 顶层 启动类加载器 在自己的类加载路径下没有找到 MyClass类,则向下退回加载 MyClass 类的请求。
  3. 扩展类加载器 收到回复就自己加载,在自己的类加载路径下也没找到 MyClass类,又向下退回 MyClass 类的加载请求给 应用程序类加载器
  4. 应用程序类加载器 于是在自己的类加载路径里找 MyClass 类,结果找到了就自己加载了。

  • 双亲委派机制 简单点解释:先找父亲加载,不行再由儿子自己加载。

3.1 示例——双亲委派机制
  • 为什么设计双亲委派机制?
  1. 沙箱安全机制:
    自定义 java.lang.String 类不会被加载,这样便可以 防止 核心API库 被随意篡改
  2. 避免类的重复加载:
    当父亲已经加载过该类时,子类加载器 就没有必要再加载一次,保证 被加载类 的唯一性
package java.lang;

/**
 * @author wy
 * describe 双亲委派机制。
 * 防止恶意代码替换`JDK`源码,为了保证安全。
 * <p>
 * AppClassLoader > ExtClassLoader > BootstrapClassLoader(最终执行)。
 * 1、类加载器 收到类加载请求。
 * 2、`AppClassLoader`将请求委托给父类加载器,一直向上委托,直到`BootstrapClassLoader`。
 * 3、`BootstrapClassLoader`开始加载,可以加载到结束,加载不到则抛出异常,通知子类加载器。
 * 4、`ExtClassLoader`开始加载,可以加载到结束,加载不到则抛出异常,通知子类加载器。
 * 4、最后`AppClassLoader`开始加载,可以加载到结束,加载不到抛出异常(`ClassNotFoundException`)。
 */
public class String {

    @Override
    public String toString() {
        return "String{}";
    }

    public static void main(String[] args) {
        String string = new String();
        string.toString();
        /*
        错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
           public static void main(String[] args)
        否则 JavaFX 应用程序类必须扩展javafx.application.Application
         */
    }
}

3.2 示例——打破双亲委派机制,验证沙箱安全机制
  • 自定义类加载器,加载自定义 java.lang.String 类。
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    // 类路径
    MyClassLoader2 classLoader = new MyClassLoader2("E:\\java\\toobox\\learn\\java-learn\\java-jvm\\src\\main\\java");
    // 包名
    Class clazz = classLoader.loadClass("java.lang.String");
    /*
    java.lang.SecurityException: Prohibited package name: java.lang
    `禁止的包名称:java.lang`
     */
}

4. 打破双亲委派机制

  • Tomcat 为了打破双亲委派机制,需要解决的问题:
  1. 一个 Web容器 可能需要 部署两个应用程序,不同的应用程序 可能会依赖 相同类库 的 不同版本
    不能要求 相同类库 在服务器只有一份,因此 每个应用程序的类库 都是独立的,要 保证相互隔离
  2. 部署在同一个 Web容器 中的应用程序,相同类库 的 相同版本 可以共享
    否则,如果服务器有 10 个应用程序,那么要有 10 份 相同的类库 加载进虚拟机。
  3. Web容器 也有 自己依赖的类库,不能与应用程序的类库混淆
    基于安全考虑,应该让 容器的类库 和 程序的类库 隔离开来
  4. Web容器 要支持 jsp 的修改,jsp文件 最终也是要编译成 class文件,才能在虚拟机中运行。
    但程序运行后修改 jsp文件 已经是司空见惯的事情,Web容器 需要支持 jsp 修改后不用重启

  • 上面问题的解决办法:
  1. 问题一,如果使用 默认的类加载机制(双亲委派),那么是 无法加载进 相同类库 的 不同版本的
    默认的类加器 是不管你是什么版本的,只在乎你的 全限定类名 只有一份。
  2. 问题二默认的类加载器 是能够实现的,因为他的职责就是 保证唯一性
  3. 问题三,和 问题一 一样。
  4. 问题四需要实现 jsp文件 的热加载,jsp文件 其实也就是 class文件。
  1. 如果修改了 jsp文件,但类名还是一样,类加载器 会直接取方法区中已经存在的。
    这样修改后的 jsp文件 是不会重新加载的。
  2. 解决办法可以 卸载掉这个 jsp文件 的类加载器
    所以每个 jsp文件 对应一个 唯一的类加载器。
  3. 当一个 jsp文件 修改了,就直接卸载这个 jsp类加载器。
    重新创建 类加载器,重新加载 jsp文件。

三、Tomcat 类加载器


1. Tomcat 四个核心类加载器

  1. CommonClassLoader 最基本的类加载器:
    加载路径中的 class文件,可以被 Tomcat容器本身 以及 各个Webapp 访问
  2. CatalinaClassLoader 容器私有的类加载器:
    加载路径中的 class文件,对于 Webapp 不可见
  3. SharedClassLoader 各个Webapp 共享的类加载器:
    加载路径中的 class文件,对于 所有Webapp 可见,但是对于 Tomcat容器 不可见
  4. WebappClassLoader 各个Webapp 私有的类加载器:
    加载路径中的 class文件,只对 当前Webapp 可见在这里插入图片描述

2. Tomcat 委派机制

  1. CommonClassLoader 加载的类,实现了公有类库的共用。
    可以被 CatalinaClassLoader 和 SharedClassLoader 使用。
  2. CatalinaClassLoader 和 SharedClassLoader 自己加载的类,则与对方相互隔离。
  3. WebAppClassLoader 可以使用 SharedClassLoader 加载到的类。
    但 各个WebAppClassLoader 实例之间 相互隔离。
  4. JasperLoader 的加载范围,仅仅是这个 jsp文件 所编译出来的那一个 class文件。
  1. JasperLoader 出现的目的就是为了被丢弃。
  2. 当 Web容器 检测到 jsp文件 被修改时,会替换掉目前的 JasperLoader 的实例。
  3. 并通过再建立一个新的 jsp类加载器,来实现 jsp文件 的热加载功能。

3. Tomcat 打破双亲委派

  • Tomcat类加载机制 违背了 Java 推荐的 双亲委派模型
  1. 双亲委派机制,要求除了 顶层的启动类加载器 之外,其余的类加载器 都应当由自己的 父类加载器 先加载。
  2. 很显然,Tomcat 不是这样实现的,Tomcat 为了实现 隔离性,没有遵守这个约定。
  • 每个 WebappClassLoader 加载自己的目录下的 class文件,不会传递给 父类加载器,打破了双亲委派机制

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑士梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值