聊聊JVM类加载那些复杂又容易误解的地方,帮你理清思路别再糊涂
- 问答
- 2026-01-02 07:43:03
- 2
聊聊JVM类加载那些复杂又容易误解的地方,帮你理清思路别再糊涂 来源:综合自网络技术博客、论坛讨论及《深入理解Java虚拟机》等经典书籍的核心观点)
类加载这东西,听起来好像就是把硬盘上的.class文件读到内存里那么简单,但真要细究起来,里面弯弯绕绕的地方可真不少,一不小心就容易想岔了,今天咱们就掰开揉碎了聊聊那些最容易让人犯晕的点。
第一个大迷糊:到底啥时候类才开始被加载?
很多人以为,你写了个new MyClass(),JVM这时候就吭哧吭哧去加载这个MyClass类了,其实不完全对。“加载”(Loading)这个动作,只是“类加载”过程的第一步,JVM规范里并没有规定啥时候必须开始加载,只规定了有且只有下面这六种情况必须立即对类进行“初始化”(Initialization),而初始化肯定是发生在加载、验证、准备之后的了,加载的时机可能比我们想的更靠前,是JVM自己说了算,有点“懒加载”的意思,但触发初始化的这六个条件你得记牢:
- 遇到
new、getstatic、putstatic或invokestatic这四条字节码指令时,简单说就是:用new实例化对象、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)、调用一个类的静态方法。 - 使用
java.lang.reflect包的方法对类进行反射调用的时候。 - 当初始化一个类时,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
- 当使用JDK 7新加入的动态语言支持时,如果一个
java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。 - 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
你看,这里面门道很多,通过子类引用父类的静态字段,只会导致父类初始化,子类不会初始化,再比如,通过数组定义来引用类,如MyClass[] arr = new MyClass[10];,这不会触发MyClass的初始化,这些细节特别容易搞混。
第二个大迷糊:“准备”阶段到底干了啥?给静态变量赋的什么值?
类加载过程的“准备”(Preparation)阶段,是给类的静态变量分配内存并设置初始值的阶段,注意,是“初始值”,不是“程序员写的那个值”!
这里有个天大的误解点:对于一般的静态变量,比如public static int value = 123;,在准备阶段,变量value被赋予的初始值是0,而不是123,那个123的赋值动作,要等到后面的“初始化”阶段才执行。

有个特例,就是被static final同时修饰的常量,即public static final int constValue = 456;,对于这种常量,如果在编译期就能确定它的值(比如456就是个字面量),那么它在准备阶段就会被直接赋值为456,这是因为final常量在编译期就已经有确定的值,并可能被优化到使用它的类的常量池中了,这个区别非常关键,经常在面试中被问到。
第三个大迷糊:到底谁在负责加载类?双亲委派模型是铁律吗?
我们老听说“双亲委派模型”,就是说一个类加载器接到加载请求后,自己不急着加载,先往上抛给父加载器去干,父加载器搞不定,自己才出手,这主要是为了保障Java核心库的类型安全,防止你自个儿写个java.lang.Object类给替换了。
但很多人误以为这是JVM的强制规定,必须遵守。双亲委派模型只是Java设计者推荐给开发者的一种类加载器实现方式,而不是一个强制性的约束模型,在Java的世界里,类加载器的核心方法是loadClass(),你去看JDK里的ClassLoader的loadClass方法,它的确实现了双亲委派的逻辑,如果你不破坏这个方法,那你的加载器就是遵守双亲委派的。

你可以通过重写loadClass()方法来打破这个模型,历史上就有打破的例子,比如Java搞出来的SPI(服务提供者接口)机制,像JDBC驱动加载,它就用到了线程上下文类加载器(Thread Context ClassLoader),这个类加载器默认是应用类加载器,它就可以逆向地去加载厂商实现的类,这就打破了双亲委派,还有OSGi这种模块化热部署框架,其类加载器是网状结构的,也需要打破双亲委派,双亲委派是主流且安全的模式,但不是唯一的模式,理解它“可被打破”这一点很重要。
第四个大迷糊:同一个类会被加载多次吗?怎么算“同一个类”?
答案是:在同一个命名空间下,对于一个类加载器实例来说,同一个全限定名的类只会被加载一次。
这里有两个关键点:“同一个类加载器”和“同一个命名空间”,如果你用两个不同的类加载器(即使是同一个类加载器的两个不同实例)去加载同一个com.example.MyClass,那么JVM会认为这是两个完全不同的类,用instanceof关键字做检查都会返回false,这就是类加载器隔离,在一些需要实现热部署或者应用隔离的场景(比如Tomcat部署多个Web应用)中会用到。
判断两个类是否“相等”,不仅要看类名是否相同,还要看它们是否由同一个类加载器加载,这打破了我们通常认为的“类名唯一”的简单认知。
类加载过程看似三步走(加载、连接、初始化),但里面充满了“和“特例”,理解触发初始化的精确时机、准备阶段赋值的真相、双亲委派的非强制性以及类唯一性的判定标准,能帮你真正捋顺JVM加载类的思路,避免很多想当然的错误,这些东西确实有点绕,但一旦想通了,你对Java程序运行机制的理解就会深一个层次。
本文由召安青于2026-01-02发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/72951.html
