本章节主要来验证下是否开启压缩指针对对象头内存布局的影响,因为在其他地方看到的结论不一样,还是得眼见为实。先看下JVM版本以及默认启动参数:
~❯ java -XX:+PrintCommandLineFlags -version -XX:G1ConcRefinementThreads=4 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC java version "11.0.1" 2018-10-16 LTS Java(TM) SE Runtime Environment 18.9 (build 11.0.1+13-LTS) Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.1+13-LTS, mixed mode)
由 -XX:+UseCompressedClassPointers
可见,默认是开启压缩指针的。下面先引入一个依赖,用于查看对象内存布局:
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.10</version> </dependency>
public class User{ int i; } public class CompressedLayout{ public static void main(String[] args){ User o = new User(); System.out.println(ClassLayout.parseInstance(o).toPrintable()); } } 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) 48 02 06 00 (01001000 00000010 00000110 00000000) (393800) 12 4 int User.i 0 Instance size: 16 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
由结果可以看出,对象一共占用16个字节,头部占12个字节。组成结构如下:
/** * vm args: -XX:-UseCompressedClassPointers * */ public class UnCompressedLayout{ public static void main(String[] args){ User o = new User(); System.out.println(ClassLayout.parseInstance(o).toPrintable()); } } cn.didadu.sample.jvm.objLayout.User 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) 28 99 20 26 (00101000 10011001 00100000 00100110) (639670568) 12 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 16 4 int User.i 0 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
由结果可以看出,对象一共占用24个字节,头部占16字节。组成结构如下:
由上面两个结果可以看出是否开启压缩指针,直接影响的是对象头部的大小,更具体的说是指向Class的类型指针那一部分。开启压缩时占4字节,未开启压缩时占8字节(64位的虚拟机)。
顺便来看下Header的前8个字节Mark World区域,这8个字节存储了对象哈希码、对象分代年龄、锁状态等。根据最后一个字节的标志位,Mark World一共有5种表现形式,下面以64位的形式来看下这5种的布局情况:
正常对象,无锁,这是新建对象的Mark World
| 25位未使用 | 31位HashCode(调用对象的hashCode后写入) | 1位未使用 | 4位age(所以age最大值为15) | 1位偏向锁标记(此处为0,表示不是偏向锁) | 2位锁状态(此处为01,结合前面1位表示未锁定) |
偏向锁对象的Mark World
| 54位线程ID | 2位epoch(偏向的时间戳) | 1位未使用 | 4位age(所以age最大值为15) | 1位偏向锁标记(此处为1,表示是偏向锁) | 2位锁状态(此处为01,结合前面1位表示可偏向) |
轻量锁的Mark World
| 62位指针,指向线程栈帧中的锁记录 | 2位锁标识(此处为00,表示轻量级锁定) |
重量级锁的Mark World
| 62位指针,指向关联的监视器对象 | 2位锁标识(此处为10,表示膨胀为重量级锁) |
被GC标记过的Mark World
| 62位空 | 2位状态标识(此处为11,表示被GC标记) |
本章节简单地验证了下对象头的内存布局,就到这儿了。