jdk11源码-synchronized

java对象头之MarkWord

直接引用openjdk的cpp源码注释

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

低3位组合标识5种状态

enum { locked_value             = 0, // ...000 // 轻量级锁
         unlocked_value           = 1, // ...001 // 无锁
         monitor_value            = 2, // ...010 重量级锁
         marked_value             = 3, // ...011 GC标识
         biased_lock_pattern      = 5 // ...101 偏向锁
  };

java调试代码

package debug.object.jol;

import debug.object.jol.bean.Bean;
import org.junit.jupiter.api.Test;
import org.openjdk.jol.info.ClassLayout;

/**
 * <p>java中一个对象包含3个部分<ul>
 *     <li>对象头 12byte<ul>
 *         <li>MarkWord 8byte</li>
 *         <li>KlassWord 4byte 压缩指针后的大小</li>
 *     </ul></li>
 *     <li>实例数据</li>
 *     <li>对齐填充</li>
 * </ul></p>
 * @since 2022/5/27
 * @author dingrui
 */
public class JOLTest {

    public static void main(String[] args) throws InterruptedException {
        for (; ; ) {
        }
    }

    /**
     * <p>VM参数-XX:-UseCompressedOops 关闭指针压缩</p>
     * <pre>
     *     +--------+------+------------------------+---------------------------------------+
     *     | OFFSET | SIZE |          DES           |                VALUE                  |
     *     +--------+------+------------------------+---------------------------------------+
     *     |    0   |   8  | (object header: mark)  | 0x0000000000000005 (biasable; age: 0) |
     *     +--------+------+------------------------+---------------------------------------+
     *     |    8   |   8  | (object header: class) |          0x000000010e1e5d98           |
     *     +--------+------+------------------------+---------------------------------------+
     * </pre>
     * <p>Instance size: 16 bytes</p>
     * <p>Space losses: 0 bytes internal + 0 bytes external = 0 bytes total</p>
     */
    @Test
    public void testLayout00() {
        Bean b = new Bean();
        System.out.println(ClassLayout.parseInstance(b).toPrintable());
    }

    /**
     * <p>VM参数-XX:+UseCompressedOops 或者使用默认参数 打开指针压缩</p>
     * <pre>
     *     +--------+------+------------------------+---------------------------------------+
     *     | OFFSET | SIZE |          DES           |                VALUE                  |
     *     +--------+------+------------------------+---------------------------------------+
     *     |    0   |   8  | (object header: mark)  | 0x0000000000000005 (biasable; age: 0) |
     *     +--------+------+------------------------+---------------------------------------+
     *     |    8   |   4  | (object header: class) |              0x0016a9f0               |
     *     +--------+------+------------------------+---------------------------------------+
     *     |   12   |   4  | (object alignment gap) |                                       |
     *     +--------+------+------------------------+---------------------------------------+
     * </pre>
     * <p>Instance size: 16 bytes</p>
     * <p>Space losses: 0 bytes internal + 4 bytes external = 4 bytes total</p>
     * <p>指针压缩 字面意思 压缩了一个指针的大小<ul>
     *     <li>32位处理器每次能处理32bit=4byte的单位->指针保存的就是4字节的一条内存地址</li>
     *     <li>64位处理器每次能处理64bit=8byte的单位->指针保存的就是8字节的一条内存地址</li>
     * </ul> 一片内存都是从低位到高位 如果总是用8byte来标识一个指针位置也就意味着在一定阈值之内64位的高位会浪费掉 完全可以用低位表示这个指针</p>
     * <p>对齐填充 总和为8bytes的倍数</p>
     */
    @Test
    public void testLayout01() {
        Bean b = new Bean();
        System.out.println(ClassLayout.parseInstance(b).toPrintable());
    }

    /**
     * <p>jdk11的JVM默认启动参数没有使用偏向延迟技术</p>
     * <p>查看jvm的参数 jinfo -flags {pid}</p>
     * <p>通过启动VM添加参数 -XX:BiasedLockingStartupDelay=4000 启动偏向锁延迟 4秒钟</p>
     * <p>延迟偏向之前创建的对象 对象头MarkWord的值0x0000000000000001 即16进制的0000000000000001 对应的二进制为1 标识的状态是无锁状态</p>
     * <p>延迟偏向之后创建的对象 对象头MarkWord的值0x0000000000000005 即16进制的0000000000000005 对应的二进制为101 标识的状态是未偏向的偏向锁</p>
     */
    @Test
    public void testLayout02() throws InterruptedException {
        Bean b = new Bean();
        System.out.println(ClassLayout.parseInstance(b).toPrintable());

        Thread.sleep(4_000);
        b = new Bean();
        System.out.println(ClassLayout.parseInstance(b).toPrintable());
    }

    /**
     * <p>偏向延迟之前创建的无锁对象 经过synchronized之后直接变成轻量级锁</p>
     */
    @Test
    public void testLayout03() throws InterruptedException {
        Object o = new Object();
        /**
         * <p>0x0000000000000001->1->无锁</p>
         */
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        synchronized (o) {
            /**
             * <p>0x000070000a7a2fc8->11100000000000000001010011110100010111111001000->轻量级锁</p>
             */
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }

    /**
     * <p>没有开启偏向延迟或者偏向延迟之后创建的对象 状态是没有偏向的偏向锁 经过synchronized之后就变成偏向当前线程的偏向锁</p>
     */
    @Test
    public void testLayout04() {
        Object o = new Object();
        /**
         * <p>0x0000000000000005->101->没有偏向线程的偏向锁</p>
         */
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        synchronized (o) {
            /**
             * <p>0x00007f8f0a80e805->11111111000111100001010100000001110100000000101->有偏向线程的偏向锁</p>
             */
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }

    /**
     * <p>当对象处于偏向锁状态时mark word总共64bits 除去低2位锁标识 1位偏向标识就剩61位 这61位中还得留高54位用于记录线程id 那就还剩7位(2位epoch 1位不用 1位gc年龄)</p>
     * <p>显然 在偏向锁状态下 是没有地方存储对象的hashcode的 也就是说hashcode值跟偏向锁状态互斥<ul>
     *     <li>第一种情况 当前线程持有对象的锁 锁状态为偏向锁 在synchronized代码块内调用hashcode之后 锁升级为轻量级锁</li>
     *     <li>第二种情况 当前对象无锁 调用hashcode之后 synchronized获取到的是轻量级锁 跳过偏向锁</li>
     * </ul></p>
     */
    @Test
    public void testLayout05() {
        Object o = new Object();
        // 0x0000000000000005->101->无偏向的偏向锁
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        synchronized (o) {
            // 0x00007ffac1019805->11111111111101011000001000000011001100000000101->偏向当前线程的偏向锁
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
            o.hashCode();
            // 0x00007ffabff13702->11111111111101010111111111100010011011100000010
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }

    /**
     * <p>完整的锁升级过程 锁的膨胀升级肯定跟竞争有关系 但是升级之后的锁状态还跟是否进行了一次hashcode方法求值有关系</p>
     */
    @Test
    public void testLayout06() {
        Object o = new Object();
        // 0x0000000000000005->101->匿名偏向锁
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        synchronized (o) {
            // 0x00007f94bf00d005->...101->偏向锁
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
        // 0x00007f94bf00d005->...101->偏向锁
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        o.hashCode();
        // 0x0000001da2cb7701->///...001->无锁
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        synchronized (o) {
            // 0x0000700002f10fc0->...000->轻量级锁
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
            o.hashCode();
            // 0x0000700002f10fc0->///...000->轻量级锁
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容