知识串联之Java对象模型与锁的关系(六)

41d351de87d39e58dd5e7207fae21c02.png

前言

上篇文章末尾抛出了一个疑问,大家了解了JMM模型,那Java对象模型又是什么?这个模型有什么用?在什么场景中使用?本文从Java对象模型作为切入点,来看看Java对象模型是什么东西,它是用来解决什么问题的。

从一个case看Java对象内存模型

创建一个简单的订单类,我们通过订单类来看看内存中的存储格式是什么样子,订单类中包含订单号、付款人和支付金额三个属性,代码如下:

public class Order {
    // 订单号
    private long orderNo;

    // 支付用户
    private long payUser;

    // 订单金额
    private long amount;

    public long getOrderNo() {
        return orderNo;
    }

    public void setOrderNo(long orderNo) {
        this.orderNo = orderNo;
    }

    public long getPayUser() {
        return payUser;
    }

    public void setPayUser(long payUser) {
        this.payUser = payUser;
    }

    public long getAmount() {
        return amount;
    }

    public void setAmount(long amount) {
        this.amount = amount;
    }
}

通过Order类创建一个对应的实例对象,实例对象在JVM内部的理论存储模型如下图所示,从图中可以看出,一个Java对象由对象头、实例数据和对齐填充三部分构成,下面我们通过jol工具实践验证一下Order对象在内存中的存储结构。

b1ba329a8ecdbfaccd700494aec08724.png

  1. 引入Maven依赖

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>
  1. 配置JVM参数,JVM默认是开启指针压缩的,该参数是用于关闭指针压缩的

-XX:-UseCompressedOops
  1. 执行如下代码

public static void main(String[] args) {
    Order order = new Order();
    String printable = ClassLayout.parseInstance(order).toPrintable();
    System.out.println(printable);
}

运行结果如下图所示,对象头主要由 Mark Word 8个字节和 Klass指针 8个字节组成;实例数据则根据内部属性类型分配相应的空间;看到这里大家会有个疑问,不是还有对齐填充吗?怎么这里没有看到,因为现在情况刚好是8的整数倍,所以不需要额外空间进行填充,自然在这里不会体现对齐填充信息。

c75b940846d13bb36bd276ba8fb42b84.png

小结 通过这个case可以看出,Java对象模型基本上由对象头(Mark Word、Klass等)、实例数据和对齐填充构成,下面我们来分别深入看看各个部分的内部构成和使用场景。

Mark Word

4862eda8c43e96ea704c9f5d7e292d9c.png

在不同锁类型下,Mark Word 的存储信息是动态变化的,下面我们来分别探讨下不同模式下 Mark Word 存储的关键信息

无锁

  1. 分代年龄(4bit):用于标识对象的GC信息,为JVM进行GC提供依据

  2. HashCode(31bit):表示当前对象的hash值

偏向锁

  1. 当前线程指针(54bit):标识持有当前对象锁的线程指针,即哪个线程可以操作当前对象,通过该线程指针即可找到对应的线程,然后通过该线程来操作当前对象完成业务逻辑的处理

  2. 分代年龄(4bit):用于标识对象的GC信息,为JVM进行GC提供依据

在JDK15中,偏向锁被废弃了,官方的解释是偏向锁为同步系统引入了许多复杂的代码,并且对HotSpot的其他组件产生了影响。还有一种解释是计算机发展到了现在,生产环境本身已处于大量的高并发场景下,使用偏向锁会使得系统带来更大的性能开销。官网地址:** https://openjdk.org/jeps/374 **

轻量级锁

  1. 指向线程栈中 lock record指针(62bit),表示在多个线程开始竞争,此时的竞争程度还不太激烈,该指针指向了获取锁成功的线程。

重量级锁

  1. monitor监视器的地址(62bit):mark word 指向一个monitor监视器的地址,monitor其实就是大名鼎鼎的synchroized在升级为重量级锁后的内部实现,其底层通过操作系统的Mutex Lock实现的,由于使用Mutex Lock需要将当前线程挂起(线程状态变为阻塞)并从用户态切换到内核态来执行,这种切换的代价非常高,这也就解释了为什么叫重量级锁(关于锁升级,monitor实现,这个我们在下篇讲synchroized的时候在详细展开)。

Klass 指针

一个Klass对象代表一个类的元数据,大白话可以理解为是创建实例对象的一个模版,那这个模版在JVM中是如何体现的呢?我们知道自己new出来的Class对象,它其实是klass包装了一层然后暴露给Java层使用,而klass是提供一个与java类对等的c++类型描述,是用于JVM内部使用的。在对象头中,通过Klass指针使得实例对象与其对应的类元数据信息关联起来,以便通过这个指针,找到该实例对象对应的元数据信息。所有的klass信息是存放在元数据信息区中。

实例数据

实例数据就是通过new创建出来的对象的具体属性值,比如文章开头Order类对应的实例,其中的orderNo、payUserId、orderAmount属性对应的具体值就是实例数据。

对齐填充

对齐填充并非必须,仅仅为了起到占位符的作用。由于HotSpot虚拟机的内存管理系统要求对象起始地址必须是8字节的整数倍,所以对象头正好是8字节的倍数。因此当对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

后续

本文重点介绍了Java对象模型的组成部分,通过对象头的中的 ** mark word ** 中存储的元数据信息,成为任何对象都可以作为锁的基础,这也是synchroized关键字实现的基础;通过对象头中的 Klass 指针 将Java层和JVM底层实现了桥接。

下篇文章我们通过分析synchroized再来看看基于Java对象头实现的锁是如何工作的。

 做一个有深度的技术人

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值