欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈
0. 常见面试题说起
- Object object = new Object()谈谈你对这句话的理解?
- 一般而言JDK8按照默认情况下,new一个对象占多少内存空间?
1. 对象在堆内存中布局
在周志明老师JVM第3版书中:
1.1 对象在堆内存中存储布局
对象在堆内存中的存储布局,如下图所示 :
对象内部结构分为:对象头、实例数据、对齐填充(保证8个字节的倍数)
。对象头分为对象标记(markOop)和类元信息(klassOop),类元信息存储的是指向该对象类元数据(klass)的首地址。
1.1.1 对象头
对象头由两部分组成:对象标记Mark Word
和类元信息(又叫类型指针)
组成。
1.1.1.1 对象标记Mark Word
默认存储对象的HashCode、分代年龄和锁标志位
等信息。
这些信息都是与对象自身定义无关的数据,所以MarkWord被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。
它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。
HotSpot虚拟机对象头Mark Word 如下表:
存储内容 | 标志位 | 状态 |
---|---|---|
对象哈希码、对象分代年龄 | 01 | 未锁定 |
指向锁记录的指针 | 00 | 轻量级锁定 |
指向重量级锁的指针 | 10 | 膨胀(重量级锁定) |
空,不需要记录信息 | 11 | GC标记 |
偏向线程D、偏向时间截、对象分代年龄 | 01 | 可偏向 |
在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。为64bit,如下:
锁状态 | 25bit | 31bit | 1bit | 4bit | 1bit | 2bit |
---|---|---|---|---|---|---|
锁状态 | cms_free | 分代年龄 | 偏向锁 | 锁标记位 | ||
无锁 | unused | hashCode | 0 | 01 | ||
偏向锁 | ThreadID(54bit) | Epoch(2bit) | 1 | 01 |
1.1.1.2 类元信息(又叫类型指针)
对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。简单理解就是Book b=new Book()中的Book.class模板:
1.1.1.3 对象头多大?
在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。
1.1.2 实例数据
存放类的属性(Field)数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。
1.1.3 对齐填充
虚拟机要求对象起始地址必须是8字节的整数倍。
填充数据不是必须存在的,仅仅是为了字节对齐,这部分内存按8字节补充对齐。
1.2 底层源码理论证明
Hotspot 术语表官网:openjdk.java.net/groups/hots…
底层源码理论证明如下:
_mark
字段是 mark word,_metadata
是类指针klass pointer,对象头(object header)即是由这两个字段组成,这些术语可以参考Hotspot术语表,
2. 再说对象头的MarkWord
32位的大家简单看一下吧:
对象标记MarkWord一共8个字节,即64位 :
markOop.hpp
如下:
上述中解释如下:
- hash: 保存对象的哈希码
- age: 保存对象的分代年龄
- biased_lock: 偏向锁标识位
- lock: 锁状态标识位
- JavaThread* :保存持有偏向锁的线程ID
- epoch: 保存偏向时间戳
markword(64位)分布图,对象布局、GC回收和后面的锁升级就是 对象标记MarkWord里面标志位
的变化:
3. 聊聊Object obj = new Object()
3.1 JOL证明
JOL官网:openjdk.java.net/projects/co…
JOL证明步骤如下:
项目引入pom依赖如下:
<!--
官网:http://openjdk.java.net/projects/code-tools/jol/
定位:分析对象在JVM的大小和分布
-->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
复制代码
代码演示如下:
public class MyObject
{
public static void main(String[] args){
//VM的细节详细情况
System.out.println(VM.current().details());
//所有的对象分配的字节都是8的整数倍。
System.out.println(VM.current().objectAlignment());
}
}
复制代码
上述代码证明,对象的大小都是8字节的整数倍。
3.2 代码演示
代码2演示如下:
public class JOLDemo
{
public static void main(String[] args)
{
Object o = new Object();
System.out.println( ClassLayout.parseInstance(o).toPrintable());
}
}
复制代码
结果说明如下:
OFFSET
: 偏移量,也就是到这个字段位置所占用的byte数SIZE
: 后面类型的字节大小TYPE
: 是Class中定义的类型DESCRIPTION
: DESCRIPTION是类型的描述VALUE
: VALUE是TYPE在内存中的值
3.3. GC年龄
GC年龄采用4位bit存储
,最大为15,例如MaxTenuringThreshold参数默认值就是15
,这是因为分代年龄占4位,最大值为1111,即15
如果设置参数:-XX:MaxTenuringThreshold=16,即分代年龄设置成16,就会报错如下:
3.4 尾巴参数说明
即说明为什么上面测试的结果对象头中的类型指针为什么只有4个字节,为什么不是8个字节?
在Idea控制台执行命令:-XX:+PrintCommandLineFlags
因为Java虚拟机默认开启了类型指针的压缩
,所以类型指针才会变成4个字节,如果不开启的话,类型指针就是8个字节。
手动关闭压缩再看看,-XX:-UseCompressedClassPointers
,结果如下:
换成其他对象试试,结果如下:
参考资料
Java并发编程的艺术
Java多线程编程核心技术
Java并发实现原理 JDK源码剖析