class对象在类加载的初始化阶段才真正创建,此时jvm动态生成并注册到方法区;只有主动使用(如new、调用静态方法、访问非常量静态字段、class.forname无参等)才会触发该阶段,而编译期常量引用或被动引用不会触发。

Class对象在类加载的“初始化”阶段才真正创建
不是类一被读进JVM就立刻有Class对象,也不是在“加载”或“验证”阶段。准确说,Class对象是在类加载过程的**初始化阶段**(initialization)首次触发时,由JVM动态生成并注册到方法区(或元空间)的——此时它才成为你能在代码里拿到的那个Class实例。
常见误解是“用了new就有Class”,其实new Foo()之前,JVM必须先完成Foo.class的初始化;而像Foo.class字面量、Class.forName("Foo")这些显式引用,只要触发了初始化,就会导致Class对象诞生。
哪些操作会真正触发Class对象创建(即触发初始化)
只有满足“主动使用”条件的操作才会启动初始化,从而生成Class对象。被动引用(比如子类引用父类的静态字段)不会触发子类初始化,也就不会创建子类的Class对象(如果还没创建过)。
-
new某个类的实例 - 调用该类的静态方法
- 访问该类的静态字段(且该字段不是常量(即非
static final编译期常量)) -
Class.forName("xxx")(注意:Class.forName("xxx", false, loader)中第二个参数为false时不会初始化) - 反射调用
getDeclaredClass()等方法本身不触发,但后续调用其newInstance()或访问静态成员会
为什么static final int X = 1不会触发初始化
因为JVM在编译期就把这种常量直接内联到调用处了,运行时根本不会去读这个类——自然也不会加载、更不会初始化,Class对象也就不会在此刻创建。你可以用 javap -c 看字节码确认:引用处直接是iconst_1,没任何对类的符号引用。
立即学习“Java免费学习笔记(深入)”;
对比一下:static final String X = "hello" 是常量,但static final String X = System.getProperty("x") 就不是编译期常量,后者一定会触发初始化。
- 常量池中存储的是字面值,不依赖类加载过程
- 一旦字段被认定为“编译期常量”,JVM连这个类的
.class文件都不一定去磁盘读 - 所以哪怕你删掉这个类的
.class文件,只要没其他主动使用,程序照样跑
ClassLoader.defineClass()之后Class对象还没初始化
手动用defineClass()把字节数组转成类,只是完成了“加载”和“连接”中的验证、准备阶段。这时Class对象已经存在(可被getClassLoader().loadClass()返回),但它的<clinit></clinit>(静态初始化块)还没执行,静态字段还是默认值(如0、null)。
- 要真正初始化,得再调一次
Class.forName("xxx", true, loader),其中第二个参数true表示“需要初始化” - 或者直接访问它的某个非final静态字段、调用静态方法——只要满足主动使用规则
- 很多热替换或字节码增强框架(如Byte Buddy)都得自己补这一句,否则看到的全是默认值
最易被忽略的一点:同一个类由不同ClassLoader加载,会产生多个互不相等的Class对象——哪怕字节码完全一样。判断是否“同一个类”,看的是Class对象的==,而不是名字。这点在OSGi、Spring Boot DevTools、自定义类加载器场景里,经常引发ClassCastException或IllegalAccessError。










