在讲述Java 对象结构之前, 我这边先引入一个我们常用的Java对象工具jol-core,jol-core
工具主要是:检查 JVM 中对象的内存布局。
第一步引入Maven包
org.openjdk.jol jol-core 0.10
第二步创建 一个简单Bean 文件,打印当前类的对象信息
#创建一个简单类
public class NormalBean {
}
#执行public static void main(String[] args) {System.System.out.println(VM.current().details());System.System.out.println("-----------------------------------------");System.err.println(ClassLayout.parseInstance(NormalBean.class).toPrintable());}
执行后 输出结果
----------------------------------结果输出-----------------------------# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
-----------------------------------------java.lang.Class object internals:OFFSET SIZE TYPE DESCRIPTION VALUE0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)8 4 (object header) df 03 00 f8 (11011111 00000011 00000000 11111000) (-134216737)12 4 java.lang.reflect.Constructor Class.cachedConstructor null16 4 java.lang.Class Class.newInstanceCallerCache null20 4 java.lang.String Class.name null24 4 (alignment/padding gap) 28 4 java.lang.ref.SoftReference Class.reflectionData null32 4 sun.reflect.generics.repository.ClassRepository Class.genericInfo null36 4 java.lang.Object[] Class.enumConstants null40 4 java.util.Map Class.enumConstantDirectory null44 4 java.lang.Class.AnnotationData Class.annotationData null48 4 sun.reflect.annotation.AnnotationType Class.annotationType null52 4 java.lang.ClassValue.ClassValueMap Class.classValueMap null56 32 (alignment/padding gap) 88 4 int Class.classRedefinedCount 092 404 (loss due to the next object alignment)
Instance size: 496 bytes
Space losses: 36 bytes internal + 404 bytes external = 440 bytes total
System.System.out.println(VM.current().details());
不难发现,输入结果中 包含两个特殊的英文
这里表示 我们当前JDK 启用 oop 压缩 模式,
oop 压缩是从 Java 7开始 的默认行为,只要最大堆大小小于 32 GB。当最大堆大小超过 32 GB 时,JVM 将自动关闭 oop 压缩。需要以不同方式管理超过 32 Gb 堆大小的内存利用率。
第二个命令是打印当前Bean详细信息 不难发现每个空Bean都有如下相同的部分。
根据上面例子我们可以看出 Java 对象布局大致如图所示
PS : 如图是(64位系统)
大致可以分为 三大部分:
在JDK源码官网地址上明确指出,对象的组成主要包含三个关键词:Mark word 、 Class word、Array Length【特殊一些】
mark word 在该实现中占用的 最小内存量:32 位平台为 4 个字节,64 位平台为 8 个字节
核心功能主要如下几点:
2.1.1.1 为什么要是设计这个东西?
VM 在处理对象的时候特别快,必须在特别毫秒级或者更短的时间快速的获取到一个对象的信息,这就是为什么它必须放到了第一位的原因。
mark word 是每个Java 对象必须存在的东西,它存储的是对象内公共的标记,.
就好比我们居民身份证: 标记 姓名、编号、性别等…
利用Demo演示 header GC 过程中 header GC年龄发生变化的情况。
第一步配置一下JVM参数 默认设置 1GB
-Xms1024m -Xmx1024m
static volatile Object sink;public static void main(String[] args) {System.out.println(VM.current().details());PrintWriter pw = new PrintWriter(System.out, true);Object o = new Object();ClassLayout layout = ClassLayout.parseInstance(o);long lastAddr = VM.current().addressOf(o);pw.printf("*** Fresh object is at %x%n", lastAddr);System.out.println(layout.toPrintable());int moves = 0;for (int i = 0; i < 100000; i++) {long cur = VM.current().addressOf(o);if (cur != lastAddr) {moves++;pw.printf("*** Move %2d, object is at %x%n", moves, cur);System.out.println(layout.toPrintable());lastAddr = cur;}// make garbagefor (int c = 0; c < 10000; c++) {sink = new Object();}}long finalAddr = VM.current().addressOf(o);pw.printf("*** Final object is at %x%n", finalAddr);System.out.println(layout.toPrintable());pw.close();}
执行结果 PS : 执行采用64位-VM 展示
*** Fresh object is at 7ad0b6980
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*** Move 1, object is at 7bab20000
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000009 (non-biasable; age: 1)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*** Move 2, object is at 7bd588a28
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000011 (non-biasable; age: 2)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*** Move 3, object is at 7bab16870
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000019 (non-biasable; age: 3)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*** Move 4, object is at 7bd59e870
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000021 (non-biasable; age: 4)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*** Move 5, object is at 7bab20520
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000029 (non-biasable; age: 5)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*** Move 6, object is at 7bfe20058
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000031 (non-biasable; age: 6)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*** Move 7, object is at 780011030
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000031 (non-biasable; age: 6)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*** Final object is at 780011030
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000031 (non-biasable; age: 6)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
结论分析
观察每个Value 值的确发送了变化 与此同时 age 不断+1
PS: 这里有一个点:关于后面两个age 有两次 6,是不是BUG?
每个 Java 对象都有一个哈希码。当没有用户定义时,将使用身份哈希码。
哈希码 两点特性:
利用Demo演示 哈希码引起的 markword 的变化。
第一步配置一下JVM参数 默认设置 1GB
-Xms1024m -Xmx1024m
public static void main(String[] args) {System.out.println(VM.current().details());Student stu = new Student();ClassLayout layout = ClassLayout.parseInstance(stu);System.out.println("---------原始对象 object");System.out.println(layout.toPrintable());System.out.println("hashCode: " + Integer.toHexString(stu.hashCode()));System.out.println();System.out.println("-----------获取完毕HashCode identityHashCode()");System.out.println(layout.toPrintable());}public static class A {// no fields}
执行结果 : PS : 执行采用64位-VM
---------原始对象 object
org.openjdk.jol.samples.JOLSample_15_IdentityHashCode$A object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) ★ 18 4 (object header: class) 0xf801339212 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes totalhashCode: 762efe5d ★ 2-----------获取完毕HashCode identityHashCode()
org.openjdk.jol.samples.JOLSample_15_IdentityHashCode$A object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x000000762efe5d01 (hash: 0x762efe5d; age: 0) ★ 38 4 (object header: class) 0xf801339212 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
结论
不难发现 在没有获取Hash之前 Header 对象【★标注 】,未执行之前 0x0000000000000001 执行以后 Header 发生了变化
0x000000762efe5d01 。
演示
利用Demo演示 BiasedLocking 【偏向锁】 引起header发生变化的情况。
**第一步配置一下JVM参数
在 JDK 9 之前,偏向锁 [BiasedLocking]仅在 5 秒后启用
* VM 启动后。 因此,测试最好用
* -XX:BiasedLockingStartupDelay=0 在 JDK 8 及更低版本上。 在 JDK 15 之后的版本,默认是关闭的 需要手动设置 -XX:+UseBiasedLocking. **
-Xms1024m -Xmx1024m -XX:BiasedLockingStartupDelay=0
public static void main(String[] args) {System.out.println(VM.current().details());final Student stu = new Student();ClassLayout layout = ClassLayout.parseInstance(stu);System.out.println("---------原始对象 object");System.out.println(layout.toPrintable());synchronized (stu) {System.out.println("---------原始对象 object 进行加锁操作");System.out.println(layout.toPrintable());}System.out.println("---------原始对象 object 进行加锁后 对象信息");System.out.println(layout.toPrintable());}public static class Student {}
执行结果
---------原始对象 object
org.openjdk.jol.samples.JOLSample_12_BiasedLocking$A object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0) ★8 4 (object header: class) 0xf801339212 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total---------原始对象 object 进行加锁操作
org.openjdk.jol.samples.JOLSample_12_BiasedLocking$A object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x00007ff9f1809005 (biased: 0x0000001ffe7c6024; epoch: 0; age: 0)★8 4 (object header: class) 0xf801339212 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total---------原始对象 object 进行加锁后 对象信息
org.openjdk.jol.samples.JOLSample_12_BiasedLocking$A object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x00007ff9f1809005 (biased: 0x0000001ffe7c6024; epoch: 0; age: 0)★8 4 (object header: class) 0xf801339212 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
结论
不难发现 在没有获取Hash之前 Header 对象【★标注 】,未执行之前 0x0000000000000005 执行以后 Header 发生了变化
0x00007ff9f1809005 。
PS:本章重点主要讲解 对象锁核心内容是什么,具体锁是如何在对象中升级以及转换
可以参考如下文章:Java锁的升级-干货教学
KLass Word 主要核心功能:
参考源码 如下: Klass C源码地址
Klass 会存储 元数据,元数据中会写入 类信息、超类、以及实现接口等信息,这些信息在编译器编译过程中会直接一些流程的检查以及应用。
我们打印一下数据
public static void main(String[] args) {System.out.println(VM.current().details());System.out.println(ClassLayout.parseInstance(new long[0]).toPrintable());for (int size = 0; size <= 8; size++) {System.out.println(ClassLayout.parseInstance(new byte[size]).toPrintable());}}
[J object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0xf80001a912 4 (array length) 016 0 long [J. N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total[B object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0xf80000f512 4 (array length) 016 0 byte [B. N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total[B object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0xf80000f512 4 (array length) 116 1 byte [B. N/A17 7 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total[B object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0xf80000f512 4 (array length) 216 2 byte [B. N/A18 6 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 6 bytes external = 6 bytes total
.......................
ta
.......................
[B object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0xf80000f512 4 (array length) 816 8 byte [B. N/A
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
随着数据不断变化 (array length) 也不断变化
下面用一个简单Demo 演示一下 数据如何进行填充的
public static void main(String[] args) {StudentOne student =new StudentOne();System.out.println(ClassLayout.parseInstance(student).toPrintable());}public static class StudentOne {private long state;}
org.openjdk.jol.samples.JOLSample_Padding$StudentOne object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0xf800c14312 4 (alignment/padding gap) 16 8 long StudentOne.state 0
Instance size: 24 bytes ★
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total ★
我们采用64位虚拟机
我们看一下 long 本身占用 8 bytes ,它的偏移 16-8=8bytes ,Header:一共 8+4=12 bytes 。
一共才 12+8=20 bytes
但是是写显示 24 bytes ,为什么呢?
原因: VM 为了使这个大小成为 8 字节的倍数,JVM 添加了 4 字节的填充。
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total ★
我们在执行之前 执行一下JVM配置测试一下 这个类
-XX:ObjectAlignmentInBytes=16
org.openjdk.jol.samples.JOLSample_Padding$StudentOne object internals:
OFF SZ TYPE DESCRIPTION VALUE0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)8 4 (object header: class) 0x00060a1812 4 (alignment/padding gap) 16 8 long StudentOne.state 024 8 (object alignment gap)
Instance size: 32 bytes
Space losses: 4 bytes internal + 8 bytes external = 12 bytes total
针对这个同一个对象 他的填充方式 按照16 bytes 进行填充,并没有截断 data 实际数据中,这样才能更好以16字节。