本条基于 Java 17(Minecraft 1.20.1 所用版本),HotSpot 行为以 JDK 17 为准。
隐藏类(Hidden Class,JEP 371,Java 15 引入)是用 MethodHandles.Lookup.defineHiddenClass 定义的类,专供框架在运行时生成、且只被框架自己使用。Lambda 表达式生成的类(形如 Foo$$Lambda$14/0x...)和 LambdaForm 类都是隐藏类。
性质
- 不可按名字发现:
Class.forName、ClassLoader.loadClass找不到它,其他类的常量池也无法按名字链接到它,只能通过defineHiddenClass返回的Class对象使用。 - 类名形如
包名.类名/0x0000000800c01000,斜杠后的后缀由 JVM 生成;Class.isHidden()返回 true。 - 默认不被其定义类加载器强引用,不可达时可以单独卸载;传入
ClassOption.STRONG则与类加载器同生命周期。 - 栈帧默认不出现在堆栈信息里。
- 不可被 Instrumentation 修改:HotSpot 的
is_modifiable_class对隐藏类返回 false,所以isModifiableClass为 false,redefineClasses 和 retransformClasses 都不能用。 - 定义时不触发 ClassFileLoadHook:HotSpot 创建类时会跳过隐藏类的这一步,已注册的 transformer 看不到它的字节码。
用 Unsafe 去除标志
隐藏状态只是 Klass 里的一个标志位,用 Unsafe 把它清掉,类就会被当作普通类,上面两个限制随之解除。思路与 klassptr 相同:先拿到 Class 对象对应的 Klass 指针,再按偏移读写其中的标志。
- JDK 17:标志是
Klass::_access_flags中的JVM_ACC_IS_HIDDEN_CLASS(0x04000000)。 - 较新的 JDK 已经把它挪到
KlassFlags里,位置和位值都不同。
注意事项
- 依赖 JDK 内部布局:Klass 的字段偏移、标志位置随 JDK 版本、构建和压缩类指针等变化,写死偏移很脆弱,一般要按版本从 HotSpot 导出的 VMStructs 信息里定位。
- 偏移或位写错会直接让 JVM 崩溃,而不是抛异常。
- 清除后只是让它“看起来像普通类”:
Class.isHidden()变为 false,但类名里仍带/0x...后缀,依赖该标志的其他行为(栈帧显示、卸载等)也会跟着改变。 - 能被 retransform / redefine 之后,仍受“只能改方法体、不能增删成员”的限制。