2026-10-31·par Sijie Wang

Class Loading

类加载:按需加载、双亲委派、打破委派做热更新

动机。 Java 运行时按需加载类:编译出的 .class 字节码,用到哪个类才变成内存里的类。好处是动态性(插件、热更新、SPI)加懒加载(启动时不用全加载)。

过程三步。 加载:读 .class 字节流,在方法区建成数据结构,同时在堆里建一个 Class 对象。链接:验证(字节码合不合法)、准备(静态字段赋默认值)、解析(符号引用换成直接引用,可以推迟)。初始化:执行 ,静态块加静态赋值按顺序来,父类先于子类。触发时机只有主动引用:new、访问静态字段和方法、反射、子类初始化、main 类。

加载器层次加双亲委派。 自顶向下:启动类加载器(Bootstrap,C++ 实现,加载核心类)、平台类加载器、应用类加载器(classpath),下面是自定义。双亲委派的意思是:先问父加载器,父找不到才自己找。两个好处:核心类永远不会被自定义的同名类替换(安全),同一个类不会被重复加载。

打破委派就是热更新的原理。 类的身份是“全限定名加加载器”,同一个类名、不同加载器就是不同的类。所以热更新的做法是:每个版本用一个新加载器加载,旧加载器一丢,旧类满足三个条件就可卸载:这个类的实例全 GC 了、Class 对象不可达、加载器本身不可达。自研 classloader 热更新那段,底子就是这个。

两个容易跟类加载搞混的邻居。

Graal:两个身份,两种“热”。做 JIT 编译器时,Graal 通过 JVMCI(JVM Compiler Interface,JVM 编译器接口)插进 HotSpot 替代 C2,它的“热”是运行时代码替换:先 profiling,再推测性优化(假设某个类型、某条分支),假设破了就去优化,退回解释执行或者重编译。换的是编译产物,不是类。做 Native Image 时,Graal 是反方向:提前编译、编译时封闭世界,运行时动态加载类受限,反射和资源都要清单。所以“Graal 可以热加载”如果指 Native Image,是反的;如果指 Graal JIT 的去优化热替换,是对的,但那不是类加载。

Lombok:编译期 trick,不是运行期。@Getter、@Setter、@Data、@Builder、@Slf4j 这些注解省样板代码。标准注解处理(JSR 269)只能生成新文件,不能改已有文件,Lombok 走得更远:它钻进 javac 的内部 API,在解析出 AST(抽象语法树)之后、生成字节码之前,直接在内存里改 AST,把方法、字段注进去。所以源码里看不见这些方法,编译出来的 class 里有。这也是它脆弱的地方:它依赖 JDK 内部 API,JDK 16 之后模块强封装(JEP 396/403),每次大版本都得适配,经常要加 --add-opens 才能跑。现在还没到不用的地步(@Builder、@Slf4j 没有 JDK 等价物),但简单的数据载体确实一部分被 record(JDK 16+)抢了。

à propos de cette entrée

L'une des entrées de wiki de sijie. L'IA sur ce site est fondée sur le même corpus et répond à la voix de sijie, avec citations revenant à des entrées comme celle-ci — répondre coûte argent à sijie, donc cela attend derrière un code : entrer un code d'accès →

Class Loading — Sijie Wang