当我们在谈对象时,我们谈的是么? ----对象布局

139 阅读6分钟

前言:本文基于64位openjdk1.8 hotspot源码展开。

想必各位Javaer对于对象应该都不陌生吧,从开始学java到步入工作,需要的时候都会自己new一个。但是大家对于对象的理解又有多少呢?今天,让我们一起来深入了解对象。

什么是对象?

我们先来看看hotspot源码中,对于对象的描述,源码位于:hotspot\src\share\vm\oops\instanceOop.hpp

大致意思就是说oopDesc 是对象类的顶级基类,也就是java中所有的对象都可以通过oopDesc进行描述。我们来看看这个类究竟长什么样?

对象布局

private:
  // MarkWord
  volatile markOop  _mark;
  // 类型指针 可能是普通直接指向MetaSpace中Klass的指针,也可能是压缩过后的指针
  union _metadata {
    Klass*      _klass;
    narrowKlass _compressed_klass;
  } _metadata;

从源码中我们可以看到两块关键的信息:MarkWord以及类型指针,而对象的数据等到使用的时候才会去进行填充.

为了更好的理解对象,这里在网上找了一张图,可以说明对象的布局:

对象头

markOop 也即是我们熟悉的markWord,存储着诸如:对象年龄、hashCode等跟对象相关的信息

Markword对应的源码位于:hotspot\src\share\vm\oops\markOop.hpp

主要关注其注释的部分,这些注释写的非常详细,给我们提供了很多有用的信息,描述了32位跟64位的大端布局

// The markOop describes the header of an object.
//
// Note that the mark is not a real oop but just a word.
// It is placed in the oop hierarchy for historical reasons.
//
// Bit-format of an object header (most significant first, big endian layout below):
//
//  32 bits:  32位对象布局
//  --------
//             --普通状态下的对象头布局
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             --偏向锁模式下的对象头布局
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//             --对象释放时的对象头布局
//             size:32 ------------------------------------------>| (CMS free block)
//             --对象从新生代升级为老年代后,原对象的对象头布局
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
//  64 bits:
//  --------  64位下未开启指针压缩时的对象布局
//             --普通状态下的对象头布局
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//             --偏向锁模式下的对象头布局
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//             --对象从新生代升级为老年代后,原对象的对象头布局
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//             --对象释放时的对象头布局
//  size:64 ----------------------------------------------------->| (CMS free block)
//
//  --------  64位下开启指针压缩时的对象布局
//             --普通状态下的对象头布局
//  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
//             --偏向锁模式下的对象头布局
//  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
//             --对象从新生代升级为老年代后,原对象的对象头布局
//  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
//             --对象释放时的对象头布局
//  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
//
//  - hash contains the identity hash value: largest value is
//    31 bits, see os::random().  Also, 64-bit vm's require
//    a hash value no bigger than 32 bits because they will not
//    properly generate a mask larger than that: see library_call.cpp
//    and c1_CodePatterns_sparc.cpp.
//
//  - the biased lock pattern is used to bias a lock toward a given
//    thread. When this pattern is set in the low three bits, the lock
//    is either biased toward a given thread or "anonymously" biased,
//    indicating that it is possible for it to be biased. When the
//    lock is biased toward a given thread, locking and unlocking can
//    be performed by that thread without using atomic operations.
//    When a lock's bias is revoked, it reverts back to the normal
//    locking scheme described below.
//
//    Note that we are overloading the meaning of the "unlocked" state
//    of the header. Because we steal a bit from the age we can
//    guarantee that the bias pattern will never be seen for a truly
//    unlocked object.
//
//    Note also that the biased state contains the age bits normally
//    contained in the object header. Large increases in scavenge
//    times were seen when these bits were absent and an arbitrary age
//    assigned to all biased objects, because they tended to consume a
//    significant fraction of the eden semispaces and were not
//    promoted promptly, causing an increase in the amount of copying
//    performed. The runtime system aligns all JavaThread* pointers to
//    a very large value (currently 128 bytes (32bVM) or 256 bytes (64bVM))
//    to make room for the age bits & the epoch bits (used in support of
//    biased locking), and for the CMS "freeness" bit in the 64bVM (+COOPs).
//
//    [JavaThread* | epoch | age | 1 | 01]       lock is biased toward given thread
//    [0           | epoch | age | 1 | 01]       lock is anonymously biased
//
//  - the two lock bits are used to describe three states: locked/unlocked and monitor.
//
//    [ptr             | 00]  locked             ptr points to real header on stack
//    [header      | 0 | 01]  unlocked           regular object header
//    [ptr             | 10]  monitor            inflated lock (header is wapped out)
//    [ptr             | 11]  marked             used by markSweep to mark an object
//                                               not valid at any other time
//    下面这注释很重要:栈、线程指针后两位都被干掉了
//    We assume that stack/thread pointers have the lowest two bits cleared.

因为我们现在接触的大多是64位的jdk,所以,下文如无特殊说明,均以64位JDK作为描述的范围。

64位JDK下的MarkOop存储的内容如下:

注释中只说明了普通对象、偏向锁时的对象、升级到老年代、被GC标志的对象的布局,但是涉及到偏向锁升级为轻量级锁、重量级锁时对象的状态并没有描述。下面的表格对这几种状态下的布局进行说明,等下也可以通过HSDB对这一过程进行证明。

开启了压缩指针的情况下:

锁状态MarkWord:64位长度分配
无锁unused:25identity_hashcode:31unused:1age:4biased_lock:1lock:2
偏向锁thread:54epoch:2unused:1age:4biased_lock:1lock:2
轻量级锁ptr_to_lock_record:62lock:2
重量级锁ptr_to_heavyweight_monitor:62lock:2
对象升级narrowOop:32unused:24cms_free:1unused:4promo_bits:3
垃圾回收

关闭压缩指针的情况下:

锁状态MarkWord:64位长度分配
无锁unused:25identity_hashcode:31cms_free:1age:4biased_lock:1lock:2
偏向锁thread:54epoch:2cms_free:1age:4biased_lock:1lock:2
轻量级锁ptr_to_lock_record:62lock:2
重量级锁ptr_to_heavyweight_monitor:62lock:2
对象升级PromotedObject*:61promo_bits:3
垃圾回收

对象头锁标志位

锁标志位值含义
00加锁
01普通对象
10重量级锁
11垃圾标记

到此,我们算是对熟悉的对象的大概布局以及对象有了一定的了解。在对象头里,也介绍了我们熟悉的锁升级过程对应的对象头的变化。尽管很多jvm书籍告诉我们,锁升级的过程就是这样这样,但真的如此吗?

欲知后事如何,且听下回分解