Java对象(堆中组成)<br>
实例变量<br>
存放类的<font color="#c41230">属性</font>数据信息,包括<font color="#c41230"><b>父类</b></font>的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。<br>
填充数据<br>
由于虚拟机要求对象起始地址必须是 8 字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
对象头
Mark Word(标记字段)
指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
Class Point(类型指针)<br>
<span style="font-size: inherit;">用于存储对象自身的</span><font color="#c41230" style="font-size: inherit;"><b>运行时数据</b></font><span style="font-size: inherit;">,它是实现<b><font color="#c41230">轻量级锁和偏向锁</font></b>的关键。它还用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。</span><br>
1.6版本变更
1.6之前:synchronized属于<b><font color="#c41230">重量级锁</font></b>,依赖于底层操作系统的 Mutex Lock 来实现。操作系统实现线程切换,需要将<font color="#c41230"><b>用户态转为核心态</b></font>,这也是早期syncchronized效率低下的原因。<br>
1.6之后:引入了<b><font color="#c41230">轻量级锁和偏向锁</font></b>,减少<b><font color="#c41230">获得锁和释放锁</font></b>所带来的性能消耗
底层原理
方法
代码块
<span style="font-size: inherit;"> 1、</span><font color="#000000" style="font-size: inherit;">执行 </font><b style="font-size: inherit; color: rgb(196, 18, 48);">monitorenter </b><span style="font-size: inherit;">指令,当前线程将试图获取对象锁所对应的 monitor 的持有权,当对象锁的 </span><b style="font-size: inherit;"><font color="#c41230">monitor 的计数器</font></b><span style="font-size: inherit;">为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。 2、倘若其他线程已经拥有对象锁的 monitor 的所有权,那当前线程将被阻塞,直到正在执行的线程执行完毕,即 <b><font color="#c41230">monitorexit</font></b> 指令被执行,执行线程将释放 monitor 并设置计数器值为 0,其他线程将有机会持有 monitor。 3、每条monitorexit命令都会对应monitorenter命令,<b><font color="#c41230">一一配对</font></b>。</span><br>
作用范围
方法
静态方法
使用的同步锁是类对象,和其他静态同步方法存在锁竞争
非静态方法
使用的锁是当前对象,同一对象之间存在锁竞争,不同对象之间不存在竞争关系
代码块
使用的锁是synchronized中传入的对象,依据是否同一对象而决定否存在竞争
锁优化
自旋锁
当线程竞争锁失败时,不直接阻塞自己,而是自旋(空等待,比如一个有限的for循环)一会儿,在自旋的同时重新竞争锁。如果在自旋结束前获得了锁,那么获取锁成功;否则自旋结束后阻塞自己
自旋锁可以减少线程阻塞造成的线程切换(包括挂起线程和恢复线程)
单核处理器情况下,不存在实际的并行。当前线程不阻塞自己,拥有锁的线程就无法执行,锁永远不会释放。进而,如果线程多而处理器少,自旋也会造成不少无谓的浪费自旋锁要占用CPU,如果是计算密集型任务,这一优化通常得不偿失,减少锁的使用是更好的选择如果锁竞争时间比较长,那么自旋通常不能获得锁,白白浪费自旋占用的CPU时间。这通常发生在所持有时间长,且竞争激烈的场景中,此时应主动禁用自旋锁
使用-XX:-UseSpinning参数关闭自旋锁优化;-XX:PreBlockSpin参数修改默认的自旋次数。
<b><font color="#c41230">自适应自旋</font></b>:对于同一个锁对象来说,如果自旋等待刚刚成功获取锁,并且持有锁的线程正在运行,那么虚拟机就会认为此次自旋也很有可能成功,进而它将允许自旋等待持续相对更长的时间,比如 100 个循环。如果对于某个锁,自旋很少成功获取过,那么在以后获取这个锁时将可能自动省略掉自旋过程,以避免浪费处理器资源。<br>
偏向锁
偏向锁的目标是减少无竞争且只有一个线程使用锁的情况下,使用轻量级锁产生的性能消耗
如果有其他线程申请锁,那么偏向锁很快就膨胀为轻量级锁。浪费了维持偏向锁的性能消耗
使用参数-XX:-UseBiasedLocking禁止偏向锁优化(默认打开)。
轻量级锁
使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功。否则可以继续使用自旋锁获取或者升级为重量级锁
轻量级锁的目标是减少无实际竞争的情况下,使用重量级锁产生的性能消耗
如果锁竞争激烈,轻量级锁很快就将升级为重量级锁,那么维持轻量级锁的过程就成了浪费
重量级锁
内置锁在Java中被抽象成监视器锁(monitor)。监视器锁直接对应底层操作系统的互斥量(mutex)
这种同步方式成本非常高,包括系统调用引起的内核态和用户态切换、线程阻塞造成的线程切换等
锁消除
<b><font color="#c41230">StringBuffer 是一个线程安全的类</font></b>,在它的 append 方法中有一个同步块,锁对象就是 sb,但是虚拟机观察变量 sb,发现它是一个<b><font color="#c41230">局部变量</font></b>,本身线程安全,并不需要额外的同步机制。因此,这里虽然有锁,但可以被安全的清除,在 JIT 编译之后,这段代码就会忽略掉所有的同步而直接执行。<br>
锁粗化
<b style="font-size: inherit;"><font color="#c41230">StringBuffer的append 方法</font></b><span style="font-size: inherit;">。如果虚拟机探测到了这样的操作,就会把加锁的同步范围扩展(粗化)到整个操作序列的外部。就是扩展到第一个 append 操作之前直至最后一个 append 操作之后,这样只需要加锁一次。</span><br>