前言
通过 JOL 工具,深入剖析对象头、实例数据以及内存对齐的具体细节,了解 JVM 是如何管理和优化内存的。使用 JOL,验证内存结构,直观地观察 JVM 参数(如对象指针压缩、类指针压缩等)对对象布局的影响。
一、工具介绍
JOL(Java Object Layout) 是一个由 OpenJDK提供的工具库,用于分析和展示 Java 对象的内存布局、对象头、对象大小以及各种内存偏移量等信息。它对于研究 Java 内存管理、理解对象布局和 JVM 内部机制非常有帮助。
JOL 的核心功能
- 对象布局分析
JOL 可以展示 Java 对象在内存中的布局细节,比如对象头、字段偏移量、实例大小等。它会根据不同的 JVM 配置(如是否启用压缩指针)给出对象的实际内存布局。 - 对象头分析
JOL能展示对象头的详细信息,包括Mark Word、类指针和数组长度等。对象头是 JVM 用来管理对象状态的重要部分,它包含锁信息、GC 状态和哈希码等内容。 - 压缩指针(Compressed Oops)支持
JOL 支持在开启和关闭指针压缩(Compressed Oops)的情况下,展示对象的内存布局差异。通过对比,可以看到指针压缩对对象大小的影响。 - 对象对齐与填充
JOL 能显示对象的内存对齐和填充情况。由于 JVM 的内存布局需要满足对齐要求(通常是 8 字节),所以某些对象可能会被填充以满足内存对齐规则。 - 数组布局
JOL 支持对数组对象的布局分析,可以查看数组对象头和每个数组元素在内存中的布局。
二、JOL使用
2.1添加JOL依赖
在Maven项目中使用以下方式添加 JOL 依赖:
<!-- 对象内存分布工具 -->
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.17</version>
</dependency>
2.2使用JOL进行对象内存分析
一个简单的测试类
package com.lazy.snail.jvm;import org.openjdk.jol.info.ClassLayout;/*** @ClassName ObjectTest* @Description TODO* @Author lazysnail* @Date 2024/11/12 16:12* @Version 1.0*/
public class ObjectTest {public static void main(String[] args) {Object obj = new Object();String layout = ClassLayout.parseInstance(obj).toPrintable();System.out.println(layout);}
}
三、对象内存布局详解
以2.2输出结果作为分析样例:
输出结果:
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0xf80001e512 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
3.1表头
-
OFF:内存布局中的偏移量
-
SZ:内存中占用的字节数
-
TYPE:数据类型(普通字段:显示具体的数据类型;对象头信息:通常为空)
-
DESCRIPTION:(普通字段:字段名称;对象头信息:mark表示标记字段、class表示类指针、gap表示内存填充)
-
VALUE:
-
对于对象头中的mark word,VALUE表示对象的标记状态,如锁状态、哈希码等。
-
对于类指针,VALUE显示的是类元数据的地址。
-
对于普通字段,VALUE显示字段的当前值。
-
3.2标记字(Mark Word)
在 64 位 JVM 中,mark word占 8 字节(64 位),在2.2的输出结果中,可以看档第一行数据便是对象头中的标记字,占用了8个字节。
mark word是对象头中一个高度复用的区域,它的内容会根据对象的状态变化而调整。它不仅仅是锁信息的存储地,还包含了对象的哈希值、GC 标记、分代年龄等重要信息。在 JVM 的内部实现中,mark word帮助实现了 Java 对象的高效锁管理和垃圾回收优化。
下表是不同状态下对象头中存储的信息:
3.3类指针
类指针也是对象头的一部分,从2.2的输出结果中可以看出,类指针占用了4字节。
3.3.1类指针位置及作用:
- 类指针指向的是该对象的类元数据。
- 类指针是对象头中的一部分,它使 JVM 能够通过这个指针找到对象的类信息:
- 字段布局:了解对象有哪些字段以及每个字段的位置和类型。
- 方法表:包含了这个类定义的所有方法,用于方法调用。
- 继承信息:包含该类的父类、接口等信息,用于类型检查和多态支持。
3.3.2类指针压缩
在 64 位 JVM 中,为了节省内存,JVM 会启用类指针压缩(Compressed Class Pointers)。当类指针压缩开启时(通过 -XX:+UseCompressedClassPointers
,通常在默认情况下开启),JVM 会将类指针压缩到 32 位以节省内存空间。这在对象数量较多的应用中可以显著减少内存消耗。
查看是否开启了类指针压缩:
#JVM运行时参数查看
java -XX:+PrintCommandLineFlags -version
结果:
-XX:ConcGCThreads=2 -XX:G1ConcRefinementThreads=8 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=268435456 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=4294967296 -XX:MinHeapSize=6815736 -XX:+PrintCommandLineFlags -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC
java version "17.0.11" 2024-04-16 LTS
Java(TM) SE Runtime Environment (build 17.0.11+7-LTS-207)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.11+7-LTS-207, mixed mode, sharing)
从结果中可以看到-XX:+UseCompressedClassPointers,确实是默认开启的。
禁用类指针压缩:
现在我们在运行2.2测试类时将类指针压缩关闭:
运行结果:
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 8 (object header: class) 0x0000000125001d30
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
可以从结果看出,当我们把类指针关闭压缩禁用之后,对象头中的类指针变成了8个字节。
3.4对齐
从2.2的运行结果中看到,"object alignment gap"存在四字节的补位对齐的情况。
这是由于HotSpot JVM 采用 8 字节对齐,本身obj实例的大小是12字节,不是8的倍数,需要补上4个字节的内存,使其占用空间变成8的倍数。
至于为什么默认采用8字节对齐,涉及到内存访问效率、CPU缓存行对齐、内存碎片化等等。
对齐策略能修改吗?能,但是真没干过,看一看效果:
运行测试类时,加上-XX:ObjectAlignmentInBytes=32
,将补齐策略修改成32位补齐
输出结果:
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0x00040fc012 20 (object alignment gap)
Instance size: 32 bytes
Space losses: 0 bytes internal + 20 bytes external = 20 bytes total
可以看出,为了满足32位的补齐策略,无端的耗费了20字节的内存。
四、总结
当执行Object obj = new Object();时,对象的内存布局主要包括以下几部分:
对象头:
- Mark Word:用于存储对象的标记信息,包括对象的哈希码、GC 状态、锁状态等。大小通常为 8 字节。
- Class Pointer:指向对象所属类(
Object
类)的指针,帮助 JVM 知道该对象的类信息。通常占 4 或 8 字节,取决于是否启用了类指针压缩。
实例数据:Object
类没有实例字段,因此这一部分为空,但仍需要空间来满足对象对齐要求。
对齐填充:为了满足 8 字节对齐要求,JVM 会在对象末尾增加填充字节(如果需要)。
[!NOTE]
对象引用本身也占用空间,大小为 4 字节或 8 字节,具体取决于是否启用了 对象引用指针压缩。