本博客的本意不是技术分享
只是在学习过程中感觉需要复习的知识点记录整理下来
方便复习,以便面试的时候用
具体细节请阅读《深入理解Java虚拟机》
若本文对你有帮助那十分荣幸。

JAVA对象的总体结构

由于Java面向对象的思想,在JVM中需要大量存储对象,存储时为了实现一些额外的功能,需要在对象中添加一些标记字段用于增强对象功能 。在学习并发编程知识synchronized时,我们总是难以理解其实现原理,因为偏向锁、轻量级锁、重量级锁都涉及到对象头,所以了解java对象头是我们深入了解synchronized的前提条件
堆内存中的java对象:

对象的几个部分的作用:

1.对象头中的Mark Word(标记字)主要用来表示对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode;
2.Klass Pointer是一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例;
3.数组长度也是占用64位(8字节)的空间,这是可选的,只有当本对象是一个数组对象时才会有这个部分;
4.实例数据是用于保存对象属性和值的主体部分,占用内存空间取决于对象的属性数量和类型;
5.对齐字是为了减少堆内存的碎片空间

获取一个对象布局实例

1 创建一个maven项目,引入pom

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

2 调用ClassLayout.parseInstance().toPrintable()


```java
@Test
void contextLoads() {L l = new L();  //new 一个对象System.out.println(ClassLayout.parseInstance(l).toPrintable());//输出 l对象 的布局
}
com.struggle.javaobject.L object internals:OFFSET  SIZE      TYPE DESCRIPTION                               VALUE0     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)                           fa bc 06 f8 (11111010 10111100 00000110 11111000) (-133776134)12     1   boolean L.myboolean                               true13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

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

对象头的组成

一个JAVA对象的存储结构。在Hotspot虚拟机中,对象在内存中的存储布局分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
在我们刚刚打印的结果中可以这样归类:

com.struggle.javaobject.L object internals:OFFSET  SIZE      TYPE DESCRIPTION                               VALUE0     4           (object header)    //markword             01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4           (object header)    //markword             00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4           (object header)   //klass pointer类元数据  fa bc 06 f8 (11111010 10111100 00000110 11111000) (-133776134)12     1   boolean L.myboolean       // Instance Data 对象实际的数据          true13     3           (loss due to the next object alignment) //Padding 对齐填充数据
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

1.Mark Word
这部分主要用来存储对象自身的运行时数据,如hashcode、gc分代年龄等。mark word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark word为32位,64位JVM为64位。
为了让一个字大小存储更多的信息,JVM将字的最低两个位设置为标记位,不同标记位下的Mark Word示意如下:


其中各部分的含义如下:
lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个mark word表示的含义不同。通过倒数三位数(包含biased_lock)我们可以判断出锁的类型

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 偏向锁
};

biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。lock和biased_lock共同表示对象处于什么锁状态。
age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
identity_hashcode:31位的对象标识hashCode,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到管程Monitor中。
thread:持有偏向锁的线程ID。
epoch:偏向锁的时间戳。
ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。
ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。

2.Klass Pointer
即对象指向它的元数据的指针,虚拟机通过这个指针来确定是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针(通过句柄池访问)。

简单引申一下对象的访问方式,我们创建对象的目的就是为了使用它。所以我们的Java程序在运行时会通过虚拟机栈中本地变量表的reference数据来操作堆上对象。但是reference只是JVM中规范的一个指向对象的引用,那这个引用如何去定位到具体的对象呢?因此,不同的虚拟机可以实现不同的定位方式。主要有两种:句柄池和直接指针。

通过句柄池访问的话,对象的类型指针是不需要存在于对象头中的,但是目前大部分的虚拟机实现都是采用直接指针方式访问。此外如果对象为JAVA数组的话,那么在对象头中还会存在一部分数据来标识数组长度,否则JVM可以查看普通对象的元数据信息就可以知道其大小,看数组对象却不行。

3 对齐填充字节
因为JVM要求java的对象占的内存大小应该是8bit的倍数,所以后面有几个字节用于把对象的大小补齐至8bit的倍数。

JVM升级锁的过程

1,当没有被当成锁时,这就是一个普通的对象,Mark Word记录对象的HashCode,锁标志位是01,是否偏向锁那一位是0。

2,当对象被当做同步锁并有一个线程A抢到了锁时,锁标志位还是01,但是否偏向锁那一位改成1,前23bit记录抢到锁的线程id,表示进入偏向锁状态。

3,当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是01,是否偏向锁是1,也就是偏向状态,Mark Word中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码。

4,当线程B试图获得这个锁时,JVM发现同步锁处于偏向状态,但是Mark Word中的线程id记录的不是B,那么线程B会先用CAS操作试图获得锁,这里的获得锁操作是有可能成功的,因为线程A一般不会自动释放偏向锁。如果抢锁成功,就把Mark Word里的线程id改为线程B的id,代表线程B获得了这个偏向锁,可以执行同步锁代码。如果抢锁失败,则继续执行步骤5。

5,偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁Mark Word的指针,同时在对象锁Mark Word中保存指向这片空间的指针。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把Mark Word中的锁标志位改成00,可以执行同步锁代码。如果保存失败,表示抢锁失败,竞争太激烈,继续执行步骤6。

6,轻量级锁抢锁失败,JVM会使用自旋锁,自旋锁不是一个锁状态,只是代表不断的重试,尝试抢锁。从JDK1.7开始,自旋锁默认启用,自旋次数由JVM决定。如果抢锁成功则执行同步锁代码,如果失败则继续执行步骤7。

7,自旋锁重试之后如果抢锁依然失败,同步锁会升级至重量级锁,锁标志位改为10。在这个状态下,未抢到锁的线程都会被阻塞。

参考和复制了以下博客:
https://blog.csdn.net/srs1995/article/details/109351177
https://blog.csdn.net/scdn_cp/article/details/86491792#comments

Java对象结构与synchronized实现原理及MarkWord详解相关推荐

  1. Java对象结构与锁实现原理及MarkWord详解

    阅读本博客前,需要了解基本的同步概念,传送门:http://note.youdao.com/noteshare?id=7f10475c6bb01658b955eaca531c0be6&sub= ...

  2. java的markword_【转帖】Java工具结构与锁实现原理及MarkWord详解

    Java工具结构与锁实现原理及MarkWord详解 https://www.pianshen.com/article/2382167638/ 我们都知道,Java工具存储在堆(Heap)内存.那么一个 ...

  3. Java对象的序列化(Serialization)和反序列化详解

    ####1.序列化和反序列化 序列化(Serialization)是将对象的状态信息转化为可以存储或者传输的形式的过程,一般将一个对象存储到一个储存媒介,例如档案或记忆体缓冲等,在网络传输过程中,可以 ...

  4. java对象头_浅谈java对象结构 对象头 Markword

    概述 对象实例由对象头.实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度; | 类型 | 32位JVM | 64位JVM| | ------ ---- | ----- ...

  5. java对象头markword_浅谈java对象结构 对象头 Markword

    概述 对象实例由对象头.实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度; | 类型 | 32位JVM | 64位JVM| | ------ ---- | ----- ...

  6. java linkedlist实例_Java Linkedlist原理及实例详解

    这篇文章主要介绍了Java Linkedlist原理及实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 定义:linkedlist属于链表结构 ...

  7. java设计模式观察者模式吗_Java设计模式之观察者模式原理与用法详解

    Java设计模式之观察者模式原理与用法详解 本文实例讲述了Java设计模式之观察者模式原理与用法.分享给大家供大家参考,具体如下: 什么是观察者模式 可以这么理解: 观察者模式定义了一种一对多的依赖关 ...

  8. redis队列优先级java实现_Redis 实现队列原理的实例详解

    Redis 实现队列原理的实例详解 场景说明: ·用于处理比较耗时的请求,例如批量发送邮件,如果直接在网页触发执行发送,程序会出现超时 ·高并发场景,当某个时刻请求瞬间增加时,可以把请求写入到队列,后 ...

  9. Spark SQL原理及常用方法详解(二)

    Spark SQL 一.Spark SQL基础知识 1.Spark SQL简介 (1)简单介绍 (2)Datasets & DataFrames (3)Spark SQL架构 (4)Spark ...

最新文章

  1. docker omv 防火墙_我的软路由折腾之旅 篇三:在openmediavault上通过Docker实现OPENWRT旁路由功能...
  2. C++ explicit关键字
  3. 呼叫中心团队管理浅谈
  4. LiveVideoStackCon2019北京参会手册
  5. Java 会是首选的最佳编程语言吗?
  6. 留个脚印,证明我来过~!
  7. Npm更新移除包的规则
  8. jdbc代码_Mysql Java 驱动代码阅读笔记及 JDBC 规范笔记
  9. jspstudy启动mysql失败_mysql启动失败的一个解决方法
  10. 程序小白天天打卡(函数模板)
  11. php apache停止工作,apache http server已停止工作解决过程
  12. 计算机控制摄像头拍照,ECap(win7及XP摄像头功能增强工具)
  13. js return加分号_js分号的重要性
  14. mac上解决Resource temporarily unavailable
  15. 宿舍的呼噜神,快看过来,我这里有药!!!
  16. Windows安装Redis并设置为开机启动
  17. 【JAVASE】IO流基础
  18. 大数据学长面试之boss直聘面试题
  19. 《疯狂java讲义》学习(44):线程同步
  20. pppoe拨号.bat

热门文章

  1. 应用统计学与计算机论文,应用统计硕士论文范文
  2. 设计全局ER模型 数据库系统原理(2007版) 课程代码4735 笔记
  3. 【历史上的今天】2 月 19 日:Hacker News 上线;Adob​​e Photoshop 诞生;DVD 退出历史舞台
  4. Markdown添加目录(自定义目录)
  5. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java学籍管理系统7uw29
  6. QQ远程提示系统权限原因暂时无法操作怎么办
  7. 电影中装逼的滚动屏幕
  8. 利用tcpcopy引流做模拟在线测试
  9. 以前的的华为手机可不可以用鸿蒙系统_现在买华为手机以后可以换成鸿蒙系统吗?...
  10. 渗透测试-Docker容器