写点什么

HotSpot JVM 「02」Java Object Layout

作者:Samson
  • 2022 年 6 月 26 日
  • 本文字数:2389 字

    阅读完需:约 8 分钟

01-Klass and OOP

HotSpot JVM 实现中用 Klass-OOP 模型来表示 Java 中的类和对象。



图 1. Klass 的继承体系



图 2. oopDesc 的继承体系

Klass 结构是 .class 文件的运行时结构; oopDesc 结构是 Object 对象的运行时结构。

oopDesc(对象头)中包含了两部分信息(更多详细的内容参考[1]):

  • mark word,存储了哈希码、锁信息、GC 元数据等。

  • klass word,类信息。注:应该是指针,指向 Metaspace 中的类结构。

  • possible alignment paddings,非必须

对象头后存储的是对象的实例数据。



图 3. oopDesc 布局示意图

JVM 中的普通对象表示为 instanceOopDesc,数组对象表示为 arrayOopDesc。两者在结构上的区别是,后者多了 4 个字节的长度信息。

02-Java Object Layout

在 32/64 位机器上,mark word 是有区别的(更多详细信息[1]):

32 bits:--------hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
64 bits:--------unused:25 hash:31 -->| unused_gap:1 age:4 biased_lock:1 lock:2 (normal object)JavaThread*:54 epoch:2 unused_gap:1 age:4 biased_lock:1 lock:2 (biased object)
复制代码


图 4. 32 bits mark word 可能的分配情况及其含义



图 5. 64 bits mark word 可能的分配情况及其含义

  • 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 升级过程(不可降级)?更多详细参考[1]

    偏向锁获取:CAS 比较线程 ID,同一线程再次获得锁的效率提高。

    偏向锁获取失败时,说明有其他线程加入抢锁的队伍,在到达 safepoint 后获得偏向锁的线程被挂起,判断锁对象是否处于锁定状态,并据此决定撤销偏向锁或升级为轻量级锁。

    轻量级锁(自旋锁):

借助工具查看 Java 对象内存布局?

  • HSDB [1]

  • jol [1, 2]

    示例

public class SimpleInt {    private int state;}
复制代码


# Running 64-bit HotSpot VM.# Objects are 8 bytes aligned.# Field sizes by type: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]# Array element sizes: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# ClassLayout.parseClass(SimpleInt.class).toPrintable()self.samson.example.jol.SimpleInt object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 16 (object header) N/A 16 4 int SimpleInt.state N/A 20 4 (loss due to the next object alignment)Instance size: 24 bytesSpace losses: 0 bytes internal + 4 bytes external = 4 bytes total# ClassLayout.parseInstance(instance).toPrintable()self.samson.example.jol.SimpleInt object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 88 13 1a 1d (10001000 00010011 00011010 00011101) (488248200) 12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 16 4 int SimpleInt.state 0 20 4 (loss due to the next object alignment)Instance size: 24 bytesSpace losses: 0 bytes internal + 4 bytes external = 4 bytes total
复制代码

03-对象创建过程

在所有会创建对象的场景中,该类的某个特定构造器方法会被调用。

  1. 创建类的实例时,会在堆上开辟空间存放所有的 instance variables,包括其超类声明的实例变量(也包括被子类 hide 的变量)。

  2. 实例变量被初始化为默认值

  3. 在新创建对象的引用被返回之前,调用特定的 constructor(先执行 instance initializers 和 instance variable initializers)

class Super {	Super() { printThree(); }	void printThree() { System.out.println("three"); }}class Test extends Super {	int three = (int)Math.PI; // That is, 3	void printThree() { System.out.println(three); }	public static void main(String[] args) {		Test t = new Test();		t.printThree();	}}// 输出// 0// 3
复制代码
  • 内存分配方式有哪些呢?

    若堆是规整的,即使用过得放在一边,空闲未用的放在一边,在两者之间存在一个指针,称为分界指示器。分配方式就是指示器向空闲部分移动一定的大小。这种方式称为指针碰撞

    若堆不是规整的,空闲内存块列表记录在空闲列表中,分配方式为从空闲列表中取合适的大小分配给对象。

  • 访问对象的方式有哪些呢?

    通过句柄访问,栈中对象的引用指向的是句柄地址,句柄包含了实例数据与数据类型信息。对象移动时更方便。

    直接访问,栈中对象的引用指向的是实例数据的地址。访问速度更高。

  • 如何判断对象已经死亡?

    引用计数,最直接,最简单,但不能解决循环引用问题,而且算法边界很多,不容易实现

    可达性分析,从 GC Root 开始,根据引用关系向下搜索。GC Root 包括:栈中引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中 JNI 引用的对象、JVM 内部的引用例如 Class 对象、所有被 synchronized 持有的对象、JMX 内部中注册的回调等等。

  • 应用类型?更多信息参考 [1]

    强引用

    软引用

    弱引用

    虚引用

  • 如何判断一个类不再使用?同时满足以下三个条件:

    类的所有实例已被回收

    类的加载器已被回收(通常难以达成,除非特殊设计目的存在,例如 OSGI、JSP 的重加载等)

    类对应的 Class 对象不被应用,也不能通过反射访问这个类


历史文章推荐

HotSpot JVM 「01」类加载、链接和初始化

发布于: 刚刚阅读数: 3
用户头像

Samson

关注

还未添加个人签名 2019.07.22 加入

还未添加个人简介

评论

发布
暂无评论
HotSpot JVM 「02」Java Object Layout_学习笔记_Samson_InfoQ写作社区