网站首页 > 技术文章 正文
JVM类加载过程
JVM类加载过程分为几个阶段,分别是 加载 、 验证 、 准备 、 解析 和 初始化 。 加载 是把二进制字节码载入内存, 验证 是校验字节流中包含的信息是否符合当要求, 准备 是为静态变量分配内存并设置静态变量初始值, 解析 是把常量池内的符号引用替换为直接引用, 初始化 是执行所有静态变量的赋值动作和静态语句块中的语句。更多详尽分析请阅读之前的文章《 JVM的类加载机制全面解析 》,这里不再赘述了。
类初始化的时机
对于我们开发人员,我认为应该具体了解一下初始化阶段什么时候在开始。JVM规范对此做了严格规范,有且只有以下5种情况必须对类进行初始化:
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类没有被初始化过,就需要先进行初始化。对于字节码指令不了解的同学,可能就是一脸蒙圈了。我们来说人话,就是:使用new关键字实例化对象的时候、读取和设置一个类的静态字段(不被final修饰的)和调用一个类的静态方法的时候。这样说更容易被理解一些。
- 使用java.lang.reflect包中的方法对类进行反射调用的时候,如果类没有被初始化过,就需要先进行初始化。
- 当初始化一个类的时候,如果发现它的父类还没有被初始化过,就需要先初始化它的父类。
- JVM会先初始化要执行的主类,也是包含main()方法的那个类。
- 当使用JDK 1.7的动态语言支持时,如果java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic(使用MethodHandle读取类的静态字段)、REF_putStatic(使用MethodHandle设置类的静态字段)、REF_invokeStatic(使用MethodHandle调用类的静态方法)的方法句柄时,如果这个方法句柄没有被初始化过,就需要先进行初始化。
被动引用
刚刚提到的5种情况,都会触发初始化,这些行为为称为对一个类的 主动引用 。除了这些以外,所有引用类的方式都不会触发初始化,被为被动引用。为了更好的理解,下面举几个被动引用的例子。
通过子类引用父类的静态变量
public class SuperClass {
static {
System.out.println("父类正在初始化");
}
public static String name = "万猫学社";
}
public class SubClass extends SuperClass {
static {
System.out.println("子类正在初始化");
}
}
public class OneMoreStudy {
public static void main(String[] args) {
System.out.println(SubClass.name);
}
}
对于静态变量,只有直接定义这个变量的类才会被初始化,通过子类引用父类中定义的静态变量,只会触发父类的初始化而不会触发子类的初始化,运行的结果是:
父类正在初始化
万猫学社
结果中并没有“子类正在初始化”。
通过数组定义来引用类
public class OneMoreStudy {
public static void main(String[] args) {
SuperClass[] arrays = new SuperClass[10];
System.out.println("数组元素个数:" + arrays.length);
}
}
这段代码中使用之前的SuperClass类,定义了一个SuperClass类的一维数组,运行后的结果是:
数组元素个数:10
结果中并没有“父类正在初始化”,说明并没有触发SuperClass类的初始化。实际上,有一个名为“[LSuperClass”的类被初始化了,它是由JVM自动生成的、直接继承于java.lang.Object,创建动作由字节码指令newarray触发。
常量
public class ConstClass {
static {
System.out.println("有常量的类正在初始化");
}
public static final String NAME = "万猫学社";
}
public class OneMoreStudy {
public static void main(String[] args) {
System.out.println(ConstClass.NAME);
}
}
常量在编译阶段会存入调用类的常量池中,本质没有直接引用到定义的常量的类,不会触发定义常量的类的初始化,所以运行的结果是:
万猫学社
结果中并没有“有常量的类正在初始化”。
接口初始化的时机
接口也有初始化过程,和类是一致的。不过接口中不能使用“static{}”语句块,但编译器仍然会为接口生成“clinit()”类构造器,用于初始化接口中所定义的成员变量。
接口初始化的时机,基本和之前提到的类的5种情况基本一致,唯一不一样的是第3种情况:在一个类被初始化时,它的父类也必须被初始化,但是一个接口被初始化时,它的父接口并不要求被初始化。只有在真正使用到父接口时才会被初始化,比如:引用父接口中定义的常量。
结语
这次主要分享了类在什么时候被初始化,共有5种情况。除了这种5种情况的引用叫做被动引用,同时举了3个被动引用的例子。同时,也提到初始化接口和类有什么不同。
猜你喜欢
- 2025-05-08 java2(JaVa2实用教程第6版Pdf)
- 2025-05-08 Java 类初始化顺序解析与高并发优化
- 2025-05-08 Java基本程序设计结构(上)(java程序设计的三种结构是什么)
- 2025-05-08 Java 阴历阳历转换(java转化日期格式)
- 2025-05-08 Kafka消息可靠传输之幂等、事务机制
- 2025-05-08 单例模式的七种写法,你都知道吗?
- 2025-05-08 Java JVM原理与性能调优:从基础到高级应用
- 2025-05-08 Spring中使用的设计模式,你都能说全吗?
- 2025-05-08 java反射之获取属性Field总结(java反射获取类名)
- 2025-05-08 字符型常量和字符串常量的区别(字符型常量和字符串常量的区别是什么?)
- 1517℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 594℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 521℃MySQL service启动脚本浅析(r12笔记第59天)
- 489℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 489℃启用MySQL查询缓存(mysql8.0查询缓存)
- 477℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 456℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 454℃MySQL server PID file could not be found!失败
- 最近发表
-
- PS所有滤镜的说明(六)(ps滤镜详解)
- 5款小白也能用的在线图片编辑器!电商效率飙升就靠它!
- Java变量(java变量有什么作用)
- Java面试常见问题:Java注解(java中的面试题)
- Java编程入门第一课:HelloWorld(java编程从入门到实践)
- Java基础教程:Java继承概述(java里继承的概述)
- java基础之——访问修饰符(private/default/protected/public)
- 如何规划一个合理的JAVA项目工程结构
- 将机器指令翻译成 JavaScript -- 终极目标
- Web 服务器基准测试:Go vs. Node.js vs. Nim vs. Bun
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- c语言min函数头文件 (68)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)