java对象头信息

java 专栏收录该内容
15 篇文章 0 订阅

做java开发几年了,但一直不知道如下问题:

1. 一个java对象到底占用了多少内存空间,应该如何计算?

2. 为什么在jdk1.6后,synchronized关键字性能有所提高,为什么会提高?并且很多文章中都说synchronized锁有偏向锁、轻量锁、重量锁等状态?

3. java对象是在那里设置了指针指向对应的方法区中的元数据的?

4. 在jvm垃圾回收时被标记为可回收但还未执行回收时,java对象是什么状态?

5. jvm怎么确定 一个java对象的GC年龄?

6. 为什么对象在经历过最多15次GC后,就会被移动到老年代中?

带着上述问题,最近终于找到了答案,于是记录了下来。

在java中,一个对象是具有相关的状态的,这状态都是保存在java对象的对象头中的。本文以64位进行说明。

1. 概述

java对象由如下几部分组成:

1. 对象头:Mark word和klasspointer两部分组成,如果是数组,还包括数组长度

2. 实例属性

3. 对齐填充

如何能看到上图结构?

注意:要打印上述内存结构图,需要引入如下依赖:

<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>

2. 对象头

64位对象头由Mark Word、klass pointer两部分组成,如果对象是数组,则还要加上数组长度,即三部分组成。

Mark Word由64位8个字节组成。

klass pointer由64位8个字节组成,但我们使用的64位 JVM会默认使用选项 +UseCompressedOops 开启指针压缩,将指针压缩至32位。即上面截图中的klass pointer为4个字节32位。

类指针klass pointer和数组长度,很简单这里不在描述,重点描述下Mark Word部分。

Mark Word的64位,不同的位表示的意思不一样,具体如下所示:

|--------------------------------------------------------------------------------------------------------------|
|                                              Object Header (128 bits)                                        |
|--------------------------------------------------------------------------------------------------------------|
|                        Mark Word (64 bits)                                    |      Klass Word (64 bits)    |       
|--------------------------------------------------------------------------------------------------------------|
|  unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  无锁
|----------------------------------------------------------------------|--------|------------------------------|
|  thread:54 |         epoch:2      | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  偏向锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_lock_record:62                            | lock:2 |     OOP to metadata object   |  轻量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_heavyweight_monitor:62                    | lock:2 |     OOP to metadata object   |  重量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                                                                      | lock:2 |     OOP to metadata object   |    GC
|--------------------------------------------------------------------------------------------------------------|

lock:  锁状态标记位,该标记的值不同,整个mark word表示的含义不同。

biased_lock:偏向锁标记,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。

age:Java GC标记位对象年龄,4位的表示范围为0-15,因此对象经过了15次垃圾回收后如果还存在,则肯定会移动到老年代中。

identity_hashcode:对象标识Hash码,采用延迟加载技术。当对象使用HashCode()计算后,并会将结果写到该对象头中。当对象被锁定时,该值会移动到线程Monitor中。

thread:持有偏向锁的线程ID和其他信息。这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。

epoch:偏向时间戳。

ptr_to_lock_record:指向栈中锁记录的指针。

ptr_to_heavyweight_monitor:指向线程Monitor的指针。

2.1 无锁状态时Mark Word-001

当一个对象才new且调用了hashcode方法后(如果不调用hashcode方法,那么存放hashcode的31位全部为0),正常情况下处于无锁状态,无锁状态时,Mark Word的64位分别为:前25位未使用,接下来的31位为对象的hashcode,接下来的1位未使用,接下来的4位表示对象的GC年龄,接下来的一位为偏向锁状态,最后2位表示锁状态。如下图所示:

2.2 偏向锁状态时的Mark Word-101

理论上而言,u对象应该是无锁状态啊,变成为偏向锁了呢?如果把sleep注释掉真的就是无锁状态。

JVM启动时会进行一系列的复杂活动,比如装载配置,系统类初始化等等。在这个过程中会使用大量synchronized关键字对对象加锁,且这些锁大多数都不是偏向锁。为了减少初始化时间,JVM默认延时加载偏向锁。这个延时的时间大概为4左右,具体时间因机器而异。当然我们也可以设置JVM参数 -XX:BiasedLockingStartupDelay=0 来取消延时加载偏向锁。

此时占用 thread 和 epoch 的 位置的均为0,说明当前偏向锁并没有偏向任何线程。此时这个偏向锁正处于可偏向状态,准备好进行偏向了!你也可以理解为此时的偏向锁是一个特殊状态的无锁

2.3 轻量级锁状态时的Mark Word-000

所谓轻量级锁是指虽然代码中有synchronized关键字加锁,但jvm在执行时,不存在并发问题,这时jvm会优化成轻量级锁,如下代码所示:

public class SyncTest {

    public static void main(String[] args) throws Exception {
        final User a = new User();

        Thread thread1 = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    System.out.println("thread1 locking");
                    System.out.println(ClassLayout.parseInstance(a).toPrintable());
                }
                try {
                    //thread1退出同步代码块,且没有死亡
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread thread2 = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    System.out.println("thread2 locking");
                    System.out.println(ClassLayout.parseInstance(a).toPrintable());
                }
            }
        };
        thread1.start();

        //让thread1执行完同步代码块中方法。
        Thread.sleep(3000);
        thread2.start();
    }
}

2.4 重量级锁状态时的Mark Word-010

即在执行代码时真的会存在锁争抢的情况,如下代码所示:

public class SyncTest {

    public static void main(String[] args) throws Exception {
        final User a = new User();

        Thread thread1 = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    System.out.println("thread1 locking");
                    System.out.println(ClassLayout.parseInstance(a).toPrintable());
                }
                try {
                    //thread1退出同步代码块,且没有死亡
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread thread2 = new Thread(){
            @Override
            public void run() {
                synchronized (a){
                    System.out.println("thread2 locking");
                    System.out.println(ClassLayout.parseInstance(a).toPrintable());
                    try {
                        //thread1退出同步代码块,且没有死亡
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread1.start();
        thread2.start();
    }
}

3.对象属性数据区

int---4个字节

long--8个字节

double--8个字节

float--4个字节

short--2个字节

char--2个字节(为什么是2个字节,不应该是一个字节么?难道跟编码有关?)

Boolean--1个字节

byte--1个字节

java对象--4个字节

4. 对齐填充区

Java对象占用空间是8字节对齐的,即所有Java对象占用字节数必须是8的倍数。如下图所示:

这个对象一个占用了24个字节,其中MarkWord+klasspointer+short+char+boolean+byte+对齐填充=18+对齐填充,而比18大且是8的整数倍的最小值为24,因此这个对象的对齐填充为6,整个对象大小为24字节。

对此,本章节前的几个问题就都有了答案,get get get!!!

 

©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页

打赏作者

cuit_618

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值