☕【JVM 技术之旅】深入 JVM 原理分析 synchronized
技术回顾
在此让我们回顾一下 Java 对象的内存结构,为什么要回顾内存结构?因为 synchronized 同步锁,采用的底层所机制的 monitor 就和内存对象有关系,所以我们先回顾一下 Java 内存结构。
Java 对象内存结构
HotSpot 虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头(Object Header)
markWord(标记字段)
用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等,这部分数据的长度在 32 位和 64 位的虚拟机(暂 不考虑开启压缩指针的场景)中分别为 32 个和 64 个 Bits。
new 一个空对象在 32 为系统中占用内存大小是 8byte(对象头,在堆中)+4byte(对象的引用地址,在栈中)=12byte;
new 一个空对象在 64 为系统中占用内存大小是 16byte(对象头,在堆中)+8byte(对象的引用地址,在栈中)=24byte;如果开启了压缩指针机制 那么就是 8byte(对象头)+4byte 的链接指针+4byte(栈中的引用地址)。
Mark Word 被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。
无锁状
加锁状态
其中轻量级锁和偏向锁是 Java 6 对 synchronized 锁进行优化后新增加的,稍后我们会简要分析。
这里我们主要分析一下重量级锁也就是通常说 synchronized 的对象锁,锁标识位为 10,其中指针指向的是 monitor 对象(也称为管程或监视器锁)的起始地址。
每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如 monitor 可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。
在 Java 虚拟机(HotSpot)中,monitor 是由 ObjectMonitor 实现的,其主要数据结构如下(位于 HotSpot 虚拟机源码 ObjectMonitor.hpp 文件,C++实现的)
ObjectMonitor 中有两个队列,WaitSet 和 EntryList,用来保存 ObjectWaiter 对象列表( 每个等待锁的线程都会被封装成 ObjectWaiter 对象),owner 指向持有 ObjectMonitor 对象的线程,当多个线程同时访问一段同步代码时
首先会进入 EntryList 集合,当线程获取到对象的 monitor 后进入 Owner 区域,并把 monitor 中的 owner 变量设置为当前线程同时 monitor 中的计数器 count 加 1。
若线程调用 wait() 方法,将释放当前持有的 monitor,owner 变量恢复为 null,count 自减 1,同时该线程进入 WaitSet 集合中等待被唤醒。
若当前线程执行完毕也将释放 monitor(锁)并复位变量的值,以便其他线程进入获取 monitor(锁)。
由此看来,monitor 对象存在于每个 Java 对象的对象头中(存储的指针的指向),synchronized 锁便是通过这种方式获取锁的,也是为什么 Java 中任意对象可以作为锁的原因,同时也是 notify/notifyAll/wait 等方法存在于顶级对象 Object 中的原因。
版权声明: 本文为 InfoQ 作者【李浩宇/Alex】的原创文章。
原文链接:【http://xie.infoq.cn/article/3376b312abdee0ce0f321efec】。文章转载请联系作者。
评论