bigywater.tech
Java · 141

Java 类加载:一个 class 文件如何变成可执行的类

一个 class 文件是怎么变成可执行的类的?这篇用 -Xlog:class+load 和几段小程序,把「什么时候加载、什么时候初始化、什么时候报错」逐项量出来,不靠背。

445 个
最简单的 HelloWorld 加载的类数
27 → 42 ms
默认 vs -Xshare:off 的进程耗时
验证阶段
没走到的分支也可能让缺失的类报错

惰性加载

实测 · 本机执行输出
  [加载] Main source: file:<jtmp>/j1a/out/
  step 1: main 开始
  step 2: 即将 new Lazy()
  [加载] Main$Lazy source: file:<jtmp>/j1a/out/
      [Lazy 静态块执行]
      Lazy.hi()
  step 3: 结束

这是 Java 系列的新一批(接在已有的 140 篇之后)的第一篇;下一篇:synchronized:从对象锁到字节码监视器。

加载与初始化

实测 · 本机执行输出
  A: Class.forName("Main$T", false, cl)
     返回了 Main$T
  B: cl.loadClass("Main$T")
     返回了
  C: T.class 字面量
     得到 T
  D: new T()
      [T 静态块执行]
     完成

缺失的类

实测 · 本机执行输出
  1) 调用 viaSameType(false)(分支没走到)
      正常返回
  2) 调用 viaSameType(true)(走到 new Dep())
      java.lang.NoClassDefFoundError: Dep
  3) 调用 Holder.m(false)(分支没走到,但方法里有 use(new Dep()),参数类型是 Base)
      java.lang.NoClassDefFoundError: Dep

HelloWorld 的类

实测 · 本机执行输出
  默认(JDK 自带 CDS 归档)         加载 445 个类:来自归档 441、来自 jrt 模块文件 3;整个进程中位耗时 27 ms
  -Xshare:off               加载 480 个类:来自归档 0、来自 jrt 模块文件 479;整个进程中位耗时 42 ms

内置加载器

实测 · 本机执行输出
  java.lang.String → null(启动类加载器)
  java.sql.Driver → platform / jdk.internal.loader.ClassLoaders$PlatformClassLoader
  Main → app / jdk.internal.loader.ClassLoaders$AppClassLoader

分步演示

类加载
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。本篇用 JDK 21.0.12
    • 没有验证:链接阶段的准备与解析的具体顺序细节(只验证了验证阶段会加载类)、自定义类加载器的 defineClass 全流程(见已有的类加载器文章)、JDK 17 与 27 的类数差异、模块层对加载的影响
    京ICP备2026067502号-1京公网安备11010502063487号