Java对象结构与锁实现原理及MarkWord详解

阅读本博客前,需要了解基本的同步概念,传送门:http://note.youdao.com/noteshare?id=7f10475c6bb01658b955eaca531c0be6&sub=590214A72B3C455FAB266C4FB7A32698

我们都知道,Java对象存储在堆(Heap)内存。那么一个Java对象到底包含什么呢?概括起来分为对象头、对象体和对齐字节。如下图所示:

对象的几个部分的作用:

1.对象头中的Mark Word(标记字)主要用来表示对象的线程锁状态,次要用途是配合GC、存放hashCode;

2.Klass Word是一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例;

3.数组长度也是占用32位(4字节)的空间,这是可选的,只有当本对象是一个数组对象时才会有这个部分;

4.对象体是用于保存对象属性和值的主体部分,占用内存空间取决于对象的属性数量和类型;

5.对齐字是为了减少堆内存的碎片空间(不一定准确)。

了解了对象的总体结构,接下来深入地了解对象头的三个部分。

一、Mark Word(标记字)

Java对象的状态主要靠Mark Word来标记,主要有5种,大部分与线程有关。这里以64位JVM为例:

|------------------------------------------------------------------------------|--------------------|
 |                                  Mark Word (64 bits)                         |       State        |
 |------------------------------------------------------------------------------|--------------------|
 | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:0 | lock:01|       正常         |
 |------------------------------------------------------------------------------|--------------------|
 | thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:01|       偏向锁       |
 |------------------------------------------------------------------------------|--------------------|
 |                       ptr_to_lock_record:62                         | lock:00|       轻量级锁     |
 |------------------------------------------------------------------------------|--------------------|
|                     ptr_to_heavyweight_monitor:62                   | lock:10|       重量级锁     |
|------------------------------------------------------------------------------|--------------------|
|                                                                     | lock:11|       GC标记       |
|------------------------------------------------------------------------------|--------------------|

以上是Java对象处于5种不同状态时,Mark Word中64个位的表现形式,上面每一行代表对象处于某种状态时的样子。其中各部分的含义如下:

lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个Mark Word表示的含义不同。状态对应如下:

biased_lock       lock            状态

0                        01              无锁

1                        01              偏向锁

                          00              轻量级锁

                          10              重量级锁

                          11              GC标记

biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。lock和biased_lock共同表示对象处于什么锁状态。

age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。

identity_hashcode:25位的对象标识Hash码,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程Monitor中。

thread:持有偏向锁的线程ID。

epoch:偏向时间戳。

ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。

ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。

以图的方式来单独描述的话,锁对象处于5种状态下的Mark Word分别表现如下:

二、Klass Word(类指针)

这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。

如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,其中,oop即ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:

  1. 每个Class的属性指针(即静态变量)
  2. 每个对象的属性指针(即对象变量)
  3. 普通对象数组的每个元素指针

当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。

三、数组长度

如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。

by@六吨代码

猜你喜欢

转载自blog.csdn.net/liudun_cool/article/details/86286872