前言
前文已经讲了虚拟机将java文件编译成class文件后的格式
java文件经过编译,形成class文件,那么虚拟机如何将这些Class文件读取到内存中呢?
加载的时机
JVM 会在程序第一次主动引用类的时候加载该类,被动引用时并不会引发类加载的操作。也就是说,JVM 并不是在一开始就把一个程序就所有的类都加载到内存中,而是到不得不用的时候才把它加载进来,而且只加载一次。
一个类的生命周期如图所示:

上图中的加载、验证、准备、初始化、卸载这几个步骤是相对固定的,但是初始化这一步不一定,他在某些情况下可以是再初始化之后执行。
加载
加载是类加载的第一阶段,虚拟机此时主要做以下三件事情:
1.通过类的全限定名来获取定义这个类的二进制字节流。
2.将字节流的静态存储结构转化为运行时的数据结构;
3.在内存中生成该类的 java.lang.Class 对象,作为方法区这个类各种数据访问入口。
主动引用一定会加载,但是被动引用则不一定
主动引用
-
遇到 new、getstatic、putstatic、invokestatic 字节码指令,例如:
使用 new 实例化对象;
读取或设置一个类的 static 字段(被 final 修饰的除外);
调用类的静态方法。
-
对类进行反射调用;
-
初始化一个类时,其父类还没初始化(需先初始化父类);
这点类与接口具有不同的表现,接口初始化时,不要求其父接口完成初始化,只有真正使用父接口时才初始化,如引用父接口中定义的常量。
-
虚拟机启动,先初始化包含 main() 函数的主类;
-
JDK 1.7 动态语言支持:一个 java.lang.invoke.MethodHandle 的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic。
被动引用
-
通过子类引用父类静态字段,不会导致子类初始化;
-
Array[] arr = new Array[10];不会触发 Array 类初始化; -
static final VAR在编译阶段会存入调用类的常量池,通过ClassName.VAR引用不会触发 ClassName 初始化。
也就是说,只有发生主动引用所列出的 5 种情况,一个类才会被加载到内存中,也就是说类的加载是 lazy-load 的,不到必要时刻是不会提前加载的,毕竟如果将程序运行中永远用不到的类加载进内存,会占用方法区中的内存,浪费系统资源。

本文详细介绍了JVM的类加载过程,包括加载、验证、准备、解析和初始化阶段。强调了类加载的时机,如主动引用和被动引用的区别。此外,还讨论了双亲委派机制,以及在JNDI服务、SPI和服务提供者加载等场景下如何打破这一机制。最后,文章提到了类加载的动态性和热部署需求对双亲委派模型的影响。
最低0.47元/天 解锁文章
1万+

被折叠的 条评论
为什么被折叠?



