第 9 章:类加载及执行子系统的案例与实战

本文最后更新于:4 天前

在 Class 文件格式与执行引擎这部分里,用户的程序能直接参与的内容并不太多Class 文件以何种格式存储,类型何时加载、如何连接,以及虚拟机如何执行字节码指令等都是由虚拟机直接控制的行为。能通过程序进行操作的,主要是字节码生成类加载器这两部分的功能。

类加载器的案例:

  • Tomact:
    • 主流的 Java Web 服务器,都实现了自己定义的类加载器而且一般还都不止一个
    • 提供了多个 ClassPath 路径供用户存放第三方类库。被放置到不同路径中的类库,具备不同的访问范围和服务对象通常每一个目录都会有一个相应的自定义类加载器去加载放置在里面的 Java 类库
    • Tomcat 的类库:
      • 隔离:
        1. 放置在 common 目录中:类库可被 Tomcat 和所有的 Web 应用程序共同使用。
        2. 放置在 server 目录中:类库可被 Tomcat 使用,对所有的 Web 应用程序都不可见。
        3. 放置在 shared 目录中:类库可被所有的 Web 应用程序共同使用,但对 Tomcat 自己不可见。
        4. 放置在 WebApp/WEB-INF 目录中:类库仅仅可以被该 Web 应用程序使用,对 Tomcat 和其他 Web 应用程序都不可见。
      • 【Tomcat 6 之前】对应隔离的类加载器:
        1. Common 类加载器。
        2. Catalina 类加载器。
        3. Shared 类加载器。
        4. WebApp 类加载器:存在多个实例。
        5. Jsp 类加载器:加载 JSP 文件编译生成的那一个 Class 文件,存在多个实例。当服务器检测到 JSP 文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 Jsp 类加载器来实现 JSP 文件的 HotSwap 功能。
      • 【Tomcat 6 及之后】对应隔离的类加载器:
        • 默认合并 common、server、shared 目录到 lib,使用 Common 类加载器加载修改配置文件可以启用原来完整的加载器架构
  • OSGi:
    • 是 OSGi 联盟制订的一个基于 Java 语言的动态模块化规范在 JDK 9 引入的 JPMS 是静态模块化系统,现在已经成为事实上动态模块化标准。
    • OSGi 中的每个模块 Bundle 之间的依赖关系从传统的上层模块依赖底层模块双亲委派模型的树形结构转变为平级模块之间运行时才能确定的网状结构的依赖:如果某个 Bundle 声明了一个它依赖的 Package,如果有其他 Bundle 声明发布这个 Package 后,那么所有对这个 Package 的类加载动作都会委派给发布它的 Bundle 类加载器去完成。
      • 【引入问题】死锁:Bundle A 依赖 Bundle B 的 Package B,且 Bundle B 依赖 Bundle A 的 Package A,双方在加载对方的类之前都会先锁定自己的类加载器的实例对象。
        【解决问题】按单线程串行化的方式强制进行类加载动作。
        • 【引入问题】牺牲了性能。
          【解决问题】JDK 7 降低了锁的级别,从类加载器的对象改为要加载的类名,从底层避免了出现死锁的可能性。

字节码生成的案例:

  • javac 命令是字节码生成技术的老祖宗
  • 使用到字节码生成技术的例子:
    • 反射。
    • 动态代理:省去了编写代理类的工作量,可以在原始类和接口还未知的时候,就确定代理类的代理行为见后面例子
      • Proxy::newProxyInstance() 方法最后调用了 java.lang.reflect.ProxyGenerator.generateProxyClass() 方法JDK 19 或 sun.misc.ProxyGenerator::generateProxyClass() 方法JDK 8,该方法会根据 Class 文件的格式规范拼装出 $Proxy0.class 的字节码实际开发中如果有要大量操作字节码的需求,还是使用封装好的字节码类库比较合适,比如 Javassist、CGLib、ASM
    • Java 逆向移植:把高版本 JDK 中编写的代码放到低版本 JDK 环境中去部署。
      • JDK 每次升级新增的功能大致可分为五类:
        1. 对 Java 类库 API 的代码增强:【模拟得比较好】以独立类库的方式实现。
        2. 在前端编译器层面做的改进语法糖:【模拟得比较好】使用 ASM 框架直接对字节码进行处理。
        3. 需要在字节码中进行支持的改动。
        4. 需要在 JDK 整体结构层面进行支持的改进。
        5. 集中在虚拟机内部的改进。

例子:动态代理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package io.weichao;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxyTest {
interface IHello {
void sayHello();
}

static class Hello implements IHello {
@Override
public void sayHello() {
System.out.println("hello world");
}
}

static class DynamicProxy implements InvocationHandler {
Object originalObj;

Object bind(Object originalObj) {
this.originalObj = originalObj;
return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(), originalObj.getClass().getInterfaces(), this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("welcome");
return method.invoke(originalObj, args);
}
}

public static void main(String[] args) {
// 生成代理类的 Class 文件:$Proxy0.class,路径:项目根目录/包名逐级文件夹/$Proxy0.class
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

IHello hello = (IHello) new DynamicProxy().bind(new Hello());
hello.sayHello();
}
}

在原始类方法执行前打印一句 welcome


第 9 章:类加载及执行子系统的案例与实战
https://weichao.io/2f1072badb21/
作者
魏超
发布于
2022年12月11日
更新于
2023年1月13日
许可协议