文章目录
Java对象内存布局和对象头
先从阿里及其它大厂面试题说起
- 你觉得目前面试,你还有哪些方面理解得比较好,我没问到的?答:juc和jvm以及同步锁机制
- 那就先说下juc,说下aqs的大致流程
- cas自旋锁,是获取不到锁就一直自旋吗?cas和synchronized区别在哪里,为什么cas好,具体优势在哪里?答:cas避免cpu切换线程的开销
- 在自旋的这个线程能保证一直占用cpu吗?假如cpu放弃了这个线程,不是还要带来线程再次抢占cpu的开销?
- sychronized底层是如何实现的,实现同步的时候用到了cas了吗?具体哪里用到了?
- 对象头存储那些信息?长度是多少位存储?
Object object = new Object()谈谈你对这句话的理解?一般而言JDK8按照默认情况下,new一个对象占用多少内存空间?
以前会问对象的位置所在?答:JVM堆->新生区->伊甸园区
现在会问对象的构成布局?提示:头体?想想我们的html报文!
对象在堆内存中布局
权威定义——周志明老师JVM第3版
在HotSpot虚拟机里,对象在堆内存的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data) 和对齐填充(Padding)。
对象在堆内存中的存储布局
- 对象头(在64位系统中,对象标记占了8个字节,类型指针占了8个字节,一共是16个字节)
- 对象标记(Mark Word)
- 默认存储对象的HashCode、分代年龄和锁标志等信息
- 这些信息都是与对象自身定义无关的数据,所以对象标记被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据
- 它会根据对象的状态复用自己的存储空间,也就是说在运行期间对象标记里存储的数据会随着锁标志位的变化而变化
- 具体存储脑图如下:
- 对象标记(Mark Word)
-
- 在64位系统中,对象标记占了8个字节,类型指针占了8个字节,一共是16个字节。所以如果只是new一个对象,这个对象没有属性,就没有实例数据,那么这个对象就是一个只有对象头的实例对象,那所占内存就是16个字节(忽略压缩指针的影响)
- 在64位系统中,对象标记占了8个字节,就是64bit大小的,其存储结构如下:
-
- 类元信息(也叫类型指针,Class Pointer)
-
-
- 存储对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象哪个类的实例
- 具体存储脑图如下:
-
- 实例数据
-
- 存放类的属性(Field)数据信息,包括父类的属性信息
- 对齐填充(保证8个字节的倍数)
-
-
虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐,这部分内存按8字节补充对齐
-
例如:
package com.bilibili.juc.objecthead; public class ObjectHeadDemo { public static void main(String[] args) { Customer customer = new Customer(); } } class Customer { int id; boolean flag = false; }
上述代码中的customer对象就是16字节(对象头,忽略压缩指针的影响)+ 4字节(int类型)+ 1字节(boolean类型)= 21字节——>对齐填充,补到24字节
-
官网理论
Hotspot术语表官网
https://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
底层源码理论证明
https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/89fb452b3688/src/share/vm/oops/oop.hpp
_mark
字段是对象标记,_metadata
是类指针klass pointer,对象头(object header)即是由这两个字段组成
另外,在 Java HotSpot™VM 中,每个对象前面都有一个类指针和一个对象标记
再说对象头的对象标记
32位虚拟机对象标记(看一下即可,不用学了,以64位为准)
64位虚拟机对象标记(重要)
C语言底层源码分析markOop.hpp
hash:保存对象的哈希码
age:保存对象的分代年龄(age:4代表4位,二进制,最大为1111,转换为十进制即15,所以GC保存对象的分代年龄就是15)
biased lock:偏向锁标识位
lock:锁状态标识位
JavaThread*:保存持有偏向锁的线程ID
epoch:保存偏向时间戳
对象标记(64位)分布图
对象布局、GC回收和后面的锁升级就是对象标记里面标志位的变化
聊聊Object obj = new Object()
JOL(Java Object LayOut)证明
JOL官网
https://openjdk.org/projects/code-tools/jol/
JOL使用
直接将以下依赖直接粘贴到maven项目的pom文件中即可
<!-- 定位:分析对象在JVM的大小和分布 -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
Code
package com.bilibili.juc.objecthead;
import org.openjdk.jol.vm.VM;
public class JOLDemo {
public static void main(String[] args) {
// Java虚拟机详细信息
System.out.println(VM.current().details());
// 所有的对象分配的字节都是8的整数倍
System.out.println(VM.current().objectAlignment());
}
}
输出结果:
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# 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]
8
获取对象的内部信息
Code
package com.bilibili.juc.objecthead;
import org.openjdk.jol.info.ClassLayout;
public class JOLDemo2 {
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
Customer1 customer1 = new Customer1();
System.out.println(ClassLayout.parseInstance(customer1).toPrintable());
Customer2 customer2 = new Customer2();
System.out.println(ClassLayout.parseInstance(customer2).toPrintable());
}
}
// 1.第一种情况,只有对象头,没有其它任何实例数据
class Customer1 {
}
// 2.第二种情况,不仅有对象头,还有实例数据,int+boolean
class Customer2 {
int id;
boolean flag = false;
}
输出结果:
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
com.bilibili.juc.objecthead.Customer1 object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 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) db cd 00 f8 (11011011 11001101 00000000 11111000) (-134165029)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
com.bilibili.juc.objecthead.Customer2 object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 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) 19 ce 00 f8 (00011001 11001110 00000000 11111000) (-134164967)
12 4 int Customer2.id 0
16 1 boolean Customer2.flag false
17 7 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
结果呈现说明
结论
- 如果一个对象只有对象头,没有其它任何实例数据,默认就是16个字节
- 如果一个对象不仅有对象头,还有实例数据,就是16字节+实例数据字节(+对齐填充)——>8的倍数
GC分代年龄
GC年龄采用4位bit存储,最大的二进制数就是1111,即十进制15,所以MaxTenuringThreshold参数默认值就是15,且最大只能是15,值应该在0000-1111之间,即0-15之间
设置GC分代年龄为16
运行报错
压缩指针
java -XX:+PrintCommandLineFlags -version:打印JVM虚拟机启动后,已启动的全部参数
默认开启压缩指针,开启后将上述类型指针压缩为4字节,以节约空间
所以上面的类型指针为4字节,加Mark Word 8字节等于12字节,不足8的倍数,(loss due to the next object alignment:对齐填充)补齐4位,即16字节
手动关闭压缩指针: -XX: -UseCompressedClassPointers,之前代码运行结果就是: