& 并发编程-3-对象头

2021/7/16 11:19:24

本文主要是介绍& 并发编程-3-对象头,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

 

toc

并发编程之synchronize原理-对象头

扫盲

存储单位的bit 和 Byte
1.bit(比特)
bit也就是我们不一定听说过的比特,大名鼎鼎的比特币就是以此命名的。它的简写为小写字母 “b” 。
作为信息技术的最基本存储单元,因为比特实在太小了,所以大家生活中并不是经常听到。那么 bit 是什么呢?
电脑是以二进制存储以及发送接收数据的。二进制的一位,就叫做 1 bit。也就是说 bit 的含义就是二进制数中的一个数位,即 “0” 或者 "1"。

2.Byte(字节)
Byte 是字节的英文写法。它的简写为大写字母 “B"。
既然名字叫字节,那肯定跟字符有关系。是的。英文字符通常是一个字节,也就是 1B,中文字符通常是两个字节,也就是 2B。
字节 Byte 和比特 bit 的换算关系是 1 Byte = 8 bit 。

1byte = 8bit
8byte = 64bit
B=byte
b=bit

自己实现一个锁

java对象头分析

所谓锁就是给对象一个标识;而这个标识就是存在对象头当中
这里截取一张hotspot的源码当中的注释

image-20210311102833000

这张图换成人可读的表格如下 此图极其重要

image-20210311102847892

java的对象头在对象的不同状态下会有不同的表现形式,主要有三种状态:无锁状态、加锁状态、GC状态。那么我可以理解java当中的加锁其实可以理解是给对象上锁,也就是改变对象头的状态,如果上锁成功则进入同步代码块。
但是java当中的锁又分为很多种,从上图可以看出大体分为偏向锁、轻量锁、重量锁三种锁状态。这三种锁的效率完全不同(依次降低)

java对象的布局以及对象头

1、利用JOL来分析java的对象布局

首先maven添加JOL的依赖

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

A.java 源码

package com.enjoy.entity;
public class A {
}

JOLExample1.java

package com.enjoy.test;
import com.enjoy.entity.A;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

public class Test1 {
  static A a= new A();
  public static void main(String[] args) {
    System.out.println(VM.current().details());
    System.out.println(ClassLayout.parseClass(A.class).toPrintable());
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
 }
}

运行结果:

image-20210311102902351

分析结果1
整个对象一共16B,其中对象头(Object header)12B,还有4B是对齐的字节(因为在64位虚拟机上对象的大小必须是8的倍数),由于这个对象里面没有任何字段,故而对象的实例数据是0B;

  1. 什么叫对象的实例数据?
  2. 对象头里面的12B到底存的是什么?
    首先要明白什么是对象的实例数据很简单:我们可以在A当中添加一个boolean的字段(boolean占1B),然后看结果:

A.java

package com.enjoy.entity;
public class A {
  boolean f;
}

运行结果2

image-20210311102916503

分析结果2
整个对象的大小还是没有改变一共16B(B=byte),其中对象头(Object header)12B,boolean字段f(对象的实例数据)占1B、剩下的3B就是对齐字节。由此我们可以认为一个对象的布局大体分为三个部分分别是对象头(Object header)、对象的实例数据字节对齐;

对象布局:

image-20210311102932268

接下来讨论第二个问题,对象头为什么是12B?这个12B当中分别存储的是什么呢?(不同位数的VM对象头的长度不一样,这里指的是64bit的vm)

2、大小端模式

再接着往下研究对象的时候先要高明白大小端存储,一般家用笔记本都是小端模式,那么什么是小端模式呢?

小端模式:高字节存在高地址,低字节存在低地址。

image-20210311103032345

3、对象头的规范

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

上述链接文档部分截图:

image-20210311103111832

image-20210311103130367

首先引用openjdk文档当中对 对象头的解释
上述引用中提到了一个java对象头包含了2个word,并且还包含了堆对象布局、类型、GC状态、同步状态和标识哈希码,具体怎么包含的呢?又是哪两个word?

对象头的结构大概如下图

image-20210311103151035

mark word为第一个word根据文档可以知他里面包含了锁的信息,hashcode,gc信息等等
klass word(klass pointer)为对象头的第二个word主要指向对象的元数据,他是一个指针;

假设我们理解一个对象头主要上图两部分组成(数组对象除外,数组对象的对象头还包含一个数组长度),那么一个java的对象头多大呢?我们从JVM的源码注释中得知到一个mark word一个是8字节, 64位,那么klass的长度是多少呢?(12-8=4byte)(对象头(Object header)一共12B)
所以我们需要想办法来获得java对象头的详细信息,验证一下他的大小,验证一下里面包含的信息是否正确。

java代码 Test1

public class Test1 {
  static A a= new A();
  //-XX:BiasedLockingStartupDelay=0
  public static void main(String[] args) throws InterruptedException {
    log.debug("----hashcode before");
    log.debug(ClassLayout.parseInstance(a).toPrintable());
    //转化成16进制,方便比较
    log.debug(Integer.toHexString(a.hashCode()));
    log.debug("----hashcode after");
    //计算完hashcode之后的a对象的布局
    log.debug(ClassLayout.parseInstance(a).toPrintable());
 }
}

运行结果

image-20210311103255722

http://tool.oschina.net/hexconvert/ 进制转换

分析结果3:

没有进行hashcode之前的对象头信息,可以看到2b-8b之前的的56bit是没有值,打印完hashcode之后行就有值了,为什么是2-8B,不应该是1-7B呢?因为是小端存储。那么第一个字节当中的八位分别存的就是分带年龄、偏向锁信息,和对象状态,这个8bit分别表示的信息如下图;这个图会随着对象状态改变而改变,下图是无锁状态下

image-20210311103309408

其中的是否可偏向标识在无锁情况下会根据是否计算hashcode而变化;
因为如果计算了hashcode之后对象便变得不可偏向;为什么?

关于对象状态一共分为五种状态,分别是无锁不可偏向、无锁可向锁、轻量锁、重量锁、GC标记,那么2bit,如何能表示五种状态(2bit最多只能表示4中状态分别是:00,01,10,11),jvm做的比较好的是把是否可偏向表示为一个状态,然后根据图中偏向锁的标识再去标识是可偏向的还是不可偏向的

00 轻量锁
01 无锁/偏向锁
10 重量锁
11 GC标记

关于对象头目前只要了解这些就够了,下节课来解释什么是偏向锁,工作原理是什么和轻量锁的性能对比

这节课只要知道对象头里面存了什么东西就可以了;
关于课上做的那个锁的膨胀的实验大家可以自己用JOL去看看,锁的膨胀非常复杂,我只是先大概的给大家演示了一下膨胀过程过程具体大概如下图(切记这个图不能作为面试,只是一个过渡结果,实际锁的膨胀远比这个复杂的多了多)

image-20210311102507109



这篇关于& 并发编程-3-对象头的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程