JVM 中类的加载(二)
温故而知新,可以为师矣
前不久又复习了一下 JVM 的类加载机制,并对上一篇类加载的博客进行了部分修正,也添加了部分知识点,第二篇也是对第一篇类加载机制博客的延伸,总结与实践;
上篇类加载机制博客指路地址:JVM 中类的加载
延伸
Klass 模型
- Java 在 JVM 中的内存形式
- Java 类在 C++ 的类 Klass
Klass 又可以分为两类:
-
非数组类
-
InstanceKlass
这是普通的类在 C++ 源码中的体现形式,类的元信息在 JVM 中对应的就是 InstanceKlass,存在于方法区中,用于存储该类的访问权限,方法信息等。
-
InstanceMirrorKlass
这是类的实例在 C++ 源码中的体现形式,存在于堆区,类的静态属性也是存储在 MirrorKlass 中的,处于运行时内存中的。
-
-
数组类
-
基本数据类型
- boolean
- char
- byte
- short
- int
- long
- float
- double
以上的基础类型对应的是 TypeArrayKlass
-
引用数据类型
对应的是 ObjArrayKlass
-
总结
链接阶段
从上一篇博客中可知,类的链接阶段分为:验证,准备和解析三个阶段。
- 验证阶段无非就是检查你这个代码有没有问题,满不满足 JVM 的约束条件;
- 准备阶段中,会给静态字段分配内存,但不会对静态字段进行初始化,静态字段的初始化会在初始阶段进行;其次要注意,如果字段被
final
修饰了,编译的时候就会给字段加上 ConstantValue 属性,就算同时被static
修饰,也不再遵循前面的规则,会直接在准备阶段完成赋值,待会进行验证。 - 解析阶段,简单来说就是将符号引用解析成直接引用,符号引用就是静态常量池的索引,直接引用就是内存地址。
初始化阶段
这里把之前我写的那些玩意儿再精炼一下:
初始化阶段就是对静态字段进行赋值,静态字段和静态代码块会被编译到一个叫做 clinit 的方法中,就算写了再多的静态代码块和静态属性,一个字节码文件最终只有一个 clinit(下文验证)。
初始化时按照静态字段和静态代码块的顺序,在 clinit 中初始化各个静态字段的值。
验证
C++ 中的 Klass 模型
一下代码来自 HotSpot 源码的 klass.hpp
文件,这个就是 Java 类在 C++ 中的模型
1 | class Klass : public Metadata { |
被 final 修饰的字段不会加入 clinit
先来段示例代码:
1 | public class Test001 { |
接下来在 IDEA 中通过 jclasslib 工具查看 JVM 字节码:
这里可以看到 静态常量d 与 常量e 被加上了 ConstantValue 属性,而静态常量d 则没有被加入到 clinit 中,说明字段d 并不会在初始化中进行赋值,而是在链接阶段中的准备阶段就已经赋值完成了。
同时可以看到这里一共有两个静态变量 b 和 c,但是只有一个 clinit;