JVM学习笔记 番外3 - java object header

JVM学习笔记 番外3 - java object header

背景

本想学习一下 java synchronized 关键字的底层原理,就想先写点代码用一下 synchronized:

package new_package.jvm.sync;

public class SynchronizedTest {

    Object lock1 = new Object();

    public static void main(String[] args) {
        SynchronizedTest s = new SynchronizedTest();
        s.test1();
    }

    public void test1() {
        synchronized (lock1) {
            System.out.println("Lock 1 ");
        }
    }
}

那么问题来了,上述代码是锁定 lock1 对象,而 lock1 对象只有一个空对象,我并没有设置任何属性,Object类内部也没有关于锁状态的内置属性, JVM 是怎么锁定 lock1 对象的呢?

哔哩哔哩大学堂

哔哩哔哩真的不止可以看番剧,还可以学计算机知识;此篇文章就是将在B站上学习到的知识梳理成资料,供以后查询学习做铺垫;

对象的内存布局

在《深入理解Java虚拟机2》中的P47页有这样一段话:

在 HotSpot 虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

自己学习Java也有6年了,从事java相关工作也有4年了,还没有听说过 java 对象头,真的要努力补充知识了。

java 对象头

刚听到 java 对象头这个概念时,我的第一反应是到 Java 虚拟机规范中去找这个概念相关的信息,但没有找到;

https://docs.oracle.com/javase/specs/jvms/se8/html/index.html

然后注意到书中说的是 在 HotSpot 虚拟机中,那是不是规范中没有特殊规定,而只是 HotSpot 的实现是这样的呢,然后在 Oracle 官网中搜 HotSpot VM ,依然没有找到:

https://www.oracle.com/java/technologies/javase-jsp.html

在 Google 上搜索 hotspot object header 关键字,第二篇是 openjdk 网站的一篇文章,里面介绍了这个概念:

https://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

object header

Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object's layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.

每个 GC 管理的堆对象开头的通用结构。(每个 oop 都指向一个对象标头。)包括有关堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本信息。由两个词(下面就是 object header 的组成部分)组成。在数组中,紧随其后的是长度字段。请注意,Java 对象和VM内部对象都具有通用的对象标头格式。

mark word

The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.

klass pointer

The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the "klass" contains a C++ style "vtable".

JOL

JOL (java object layout) java 对象布局(即java对象在内存中数据存储格式);

jol-core 是一个库,可以使用它打印对象数据结构信息:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>
package new_package.jvm.sync;

import org.openjdk.jol.info.ClassLayout;

public class SynchronizedTest2 {

    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}
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

从上述输出信息可以知道,object header 占了 12byte 长度;

➜  ~ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

友情提示:上面的输出信息中有这样一句话:loss due to the next object alignment,这是因为当前版本的 hotspot (HotSpot 64-Bit Server VM build 25.181-b13)有一个机制,当对象内存不足 8byte 整数倍的时候,会自动补齐,即凑足 8byte 整数倍。

本机测试环境为 MacxOs / jdk1.8 / Java HotSpot™ 64-Bit Server VM

object header 的组成

提示: openjdk 就是 hotspot VM开源部分的一个项目;

openjdk 的那篇文章中只说了包含了那些内容,并没有详细说明;

此时我们可以下载一下 openjdk 的源码查看更多的细节知识;

在 ~/openjdk-8-src-b132/hotspot/src/share/vm/oops 目录下,查看 markOop.hpp 和 klass.hpp 文件;


markOop.hpp 节选:

// 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:
//  --------
//             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:
//  --------
//  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)
//
//  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.

从源码中可得知,在 32bit 的 VM 中, mark word 就是 32bit(4byte),而在 64bit 的 VM 中,mark word 就是 64bit(8byte);
总的 object header 长度(64bit VM)为 12byte ,所以 klass pointer 部分为 4byte;

学习教程中说到了一个概念,叫指针压缩
还指出 HotSpot VM 默认是开启了指针压缩的,所以在 64bit Vm 中 object header 才只占了 12byte,不开启指针压缩情况下,会占 16byte(mark 8byte,klass pointer 8byte);即开启了指针压缩会将 klass pointer 部分的内容进行压缩,而不会影响 mark word 部分;


markOop.hpp 节选:

//  Klass layout:
//    [C++ vtbl ptr  ] (contained in Metadata)
//    [layout_helper ]
//    [super_check_offset   ] for fast subtype checks
//    [name          ]
//    [secondary_super_cache] for fast subtype checks
//    [secondary_supers     ] array of 2ndary supertypes
//    [primary_supers 0]
//    [primary_supers 1]
//    [primary_supers 2]
//    ...
//    [primary_supers 7]
//    [java_mirror   ]
//    [super         ]
//    [subklass      ] first subclass
//    [next_sibling  ] link to chain additional subklasses
//    [next_link     ]
//    [class_loader_data]
//    [modifier_flags]
//    [access_flags  ]
//    [last_biased_lock_bulk_revocation_time] (64 bits)
//    [prototype_header]
//    [biased_lock_revocation_count]
//    [_modified_oops]
//    [_accumulated_modified_oops]
//    [trace_id]

object header 之 mark word

前提 : 64 bits HotSpot VM

//  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)
//
//  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)

偏斜的锁定模式用于将锁定偏向给定的线程。 当此模式设置为低三位时,锁要么偏向给定线程,要么“匿名”偏向,这表明有可能偏向锁。 当锁偏向给定线程时,可以在不使用原子操作的情况下由该线程执行锁定和解锁。 取消锁的偏向后,它会恢复为以下所述的普通锁方案。
请注意,我们正在重载标头的“未锁定”状态的含义。 因为我们从年龄中偷了一些东西,所以我们可以保证对于真正解锁的对象永远不会看到偏差模式。
还要注意,偏置状态包含通常包含在对象标头中的年龄位。 当这些位不存在并且为所有有偏向的对象分配了任意年龄时,发现清理时间会大大增加,因为它们倾向于消耗大量的伊甸半空间并且没有被迅速提升,从而导致执行的复制数量增加。 运行时系统将所有JavaThread *指针对齐到一个非常大的值(当前为128字节(32bVM)或256字节(64bVM)),以便为年龄位和纪元位(用于支持有偏锁)留出空间。 64bVM中的CMS“自由度”位(+ COOP)。

小测试 hashcode

package new_package.jvm.sync;

import org.openjdk.jol.info.ClassLayout;

public class SynchronizedTest3 {

    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(Integer.toHexString(obj.hashCode()));
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}
4dc63996

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 96 39 c6 (00000001 10010110 00111001 11000110) (-969304575)
      4     4        (object header)                           4d 00 00 00 (01001101 00000000 00000000 00000000) (77)
      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

将结果进行对比:

4d c6 39 96 (hashcode)

01 96 39 c6 4d 00 00 00 e5 01 00 f8 (object header)

markOop.hpp 节选中有这样一句话:(most significant first, big endian layout below 大端模式 是指数据的高字节保存在内存的低地址中)


所以我们将 object header 结果进行反向查看:

f8 00 01 e5 (klass)
00 00 00 4d (mark)
c6 39 96 01 (mark)

mark 部分对应一下 normal object 模式:

unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2

小测试 synchronized

package new_package.jvm.sync;

import org.openjdk.jol.info.ClassLayout;

public class SynchronizedTest3 {

    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(Integer.toHexString(obj.hashCode()));
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());

        synchronized (obj){
            System.out.println("=======");
            System.out.println(Integer.toHexString(obj.hashCode()));
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        }
    }
}
4dc63996
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 96 39 c6 (00000001 10010110 00111001 11000110) (-969304575)
      4     4        (object header)                           4d 00 00 00 (01001101 00000000 00000000 00000000) (77)
      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

=======
4dc63996
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           90 69 23 08 (10010000 01101001 00100011 00001000) (136538512)
      4     4        (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      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

对比加锁前后对象头的变化,仅对比 mark word 部分:

00 00 00 4d c6 39 96 01 (normal object)
00 00 70 00 08 23 69 90 (?)

很大的变化,连 hashcode 都不存了,至于是哪种模式,有待进一步学习。。。


1更:

00 00 00 4d c6 39 96 01 (00000001 normal object)
00 00 70 00 08 23 69 90 (10010000 轻量级锁)
参考:https://blog.csdn.net/u013837825/article/details/105767599

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值