Welcome everyone

介绍一种Java对象布局小工具

java 汪明鑫 150浏览 0评论

JAVA对象布局

上图是堆中的Java对象的布局

Java对象由对象头和对象体组成,对了,最后面还会有个8字节按需对齐

对象头由Mark Word和Klass Pointer组成以及数组长度组成

数组长度只有对象是数组才会有值

Klass Pointer指向对象的Class信息

Mark Word 存储对象自身的一些数据,如锁信息、hashCode、gc年龄等

 

 

maven引入

<dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>

 

JOL初体验

package pers.wmx.springbootfreemarkerdemo.jol;

import org.openjdk.jol.info.ClassLayout;

/**
 * JOL(Java Object Layout)
 * 查看对象的内存布局、内存踪迹和引用
 *
 * @author wangmingxin03
 * Created on 2021-11-12
 */
public class Main1 {

    public static void main(String[] args) {
        A a = new A();
        System.out.println("a : " + ClassLayout.parseInstance(a).toPrintable());
    }

    static class A {
        private int x = 10;
    }
}

 

关键方法 ClassLayout.parseInstance(a).toPrintable()

 

a : pers.wmx.springbootfreemarkerdemo.jol.Main1$A 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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4    int A.x                                       10
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

看到objetc header 12字节,12 * 8 = 96bit

对象头占据12字节,大家可能看到一些文章写的是16字节 16 * 8 = 128bit

因为新版Jdk默认开启了指针压缩,所以是96bit

|--------------------------------------------------------------|
|                     Object Header (96 bits)                  |
|------------------------------------|-------------------------|
|        Mark Word (64 bits)         | Klass pointer (32 bits) |
|------------------------------------|-------------------------|

 

 

可以关闭指针压缩 -XX:-UseCompressedOops

 

 

再run一下

a : pers.wmx.springbootfreemarkerdemo.jol.Main1$A 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)                           a0 a5 15 1d (10100000 10100101 00010101 00011101) (487957920)
     12     4        (object header)                           02 00 00 00 (00000010 00000000 00000000 00000000) (2)
     16     4    int A.x                                       10
     20     4        (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

发现对象头16个字节了

 

|--------------------------------------------------------------|
|                     Object Header (128 bits)                 |
|------------------------------------|-------------------------|
|        Mark Word (64 bits)         | Klass pointer (64 bits) |
|------------------------------------|-------------------------|

 

  • OFFSET:偏移地址,单位字节;
  • SIZE:占用的内存大小,单位为字节;
  • TYPE DESCRIPTION:类型描述,其中object header为对象头;
  • VALUE:对应内存中当前存储的值;

 

从锁升级看对象头

锁升级的细节,本文不做详细的介绍,太复杂了,其实我也没太搞明白,简单通过demo看下锁升级和认识下对象头

 

package pers.wmx.springbootfreemarkerdemo.jol;

import java.util.concurrent.TimeUnit;

import org.openjdk.jol.info.ClassLayout;

/**
 * JOL(Java Object Layout)
 * 查看对象的内存布局、内存踪迹和引用
 *
 * @author wangmingxin03
 * Created on 2021-11-12
 */
public class Main {

    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());  // 001 (无锁)

        synchronized (o) {
            System.out.println(ClassLayout.parseInstance(o).toPrintable()); // 000 (轻量级锁)
        }

        Thread t1 = new Thread(() -> {
            synchronized (o) {
                System.out.println("t1 lock " + Thread.currentThread().getName());
                System.out.println(ClassLayout.parseInstance(o).toPrintable());  // 010 (重量级锁)
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (o) {
                System.out.println("t2 lock " + Thread.currentThread().getName());
                System.out.println(ClassLayout.parseInstance(o).toPrintable());  // 010 (重量级锁)
            }
        });

        t1.start();
        t2.start();

        Thread.sleep(TimeUnit.SECONDS.toMillis(5));
        // 线程都执行完,释放锁
        System.out.println("after thread lock : " + ClassLayout.parseInstance(o).toPrintable()); // 001 (无锁)
    }
}

 

上面给了简单的demo, 对象一开始创建是无锁

一个线程加锁到轻量锁

多线程竞争加锁到重量锁

 

从后往前看,看最后三位 001 表示无锁

 

在底层源码中有这样的一个枚举定义

enum {  locked_value              = 0, // 0 00 轻量级锁
         unlocked_value           = 1,// 0 01 无锁
         monitor_value            = 2,// 0 10 重量级锁
         marked_value             = 3,// 0 11 gc标志
         biased_lock_pattern      = 5 // 1 01 偏向锁
  };

 

这64bit在锁升级过程中位的分配还有不同

上图可以看到当锁升级到轻量级锁,mark word存储的变成了争抢到锁的线程的线程栈中的锁记录(Lock Record)的指针了

mark word存储的是哪个线程的lock record,就是哪个线程抢到了锁

肯能有兄弟要问了,那以前的那些信息存哪去了?Lock Record会存储指向之前状态的对象的mark ward信息的指针

 

转载请注明:汪明鑫的个人博客 » 介绍一种Java对象布局小工具

喜欢 (2)

说点什么

您将是第一位评论人!

提醒
avatar
wpDiscuz