理解JVM(5)锁
来源:互联网 发布:百科题库 软件下载 编辑:程序博客网 时间:2024/06/08 20:13
对象头和锁
JVM的实现中,每个对象都有一个对象头,用于保存对象的系统信息。对象头中有一个Mark Word部分,里面存放对象的哈希值,对象的年龄,锁的指针,是否占用锁,哪个锁等信息
在32位系统中,Mark Word占32位,这是小端储存,从右往左看。默认状态下,对象前2位总是状态位,第三位表示是否是偏向锁,看得到不是一定有的。无锁,第4-6位会存放对象年龄,8-32位放Hash值。
// 32 bits:// --------// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)// size:32 ------------------------------------------>| (CMS free block)// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)//// 64 bits:// --------// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)// size:64 ----------------------------------------------------->| (CMS free block)//// unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)// JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)// narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)// unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
锁在JVM中的优化
为了避免在操作系统层面的挂起线程,JVM自己优先解决问题,办法有n种
偏向锁
产生的原因是,大多数时候加锁只是一个保护性的措施,大多数时候并不会出现竞态。而真正出现了竞态情况,才会退出偏向模式。
启用参数:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0(表示马上启动,默认在4秒后)。在竞态环境强的时候,频繁进退偏向模式会消耗时间,可以禁用偏向锁优化。
我理解的原因是,当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁,而只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果测试成功,表示线程已经获得了锁,如果测试失败,则需要再测试下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁),如果没有设置,则使用CAS竞争锁,如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
偏向锁的撤销:偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态,如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。
轻量锁
如果偏向锁失败,JVM会让线程申请轻量锁。轻量锁在JVM内部是以BasicObjectLock的对象来实现的.其中有lockField和objField,该对象存在于线程私有的Java栈里面。而BasicLock类中存放对象头部的Mark Word的备份。
public class BasicObjectLock extends VMObject { private static sun.jvm.hotspot.types.Field lockField; private static sun.jvm.hotspot.types.OopField objField; private static int size;}public class BasicLock extends VMObject { private static CIntegerField displacedHeaderField; public Mark displacedHeader() { return new Mark(addr.addOffsetTo(displacedHeaderField.getOffset())); }}//C++部分实现markOop mark = obj->mark();lock->set_displaced_header(mark);if (mark == (markOop) Atomic::cmpxchg_ptr(lock,obj()->mark_addr(),mark)){ TEVENT (show_enter: release stacklock);}
首先BasicLock通过set_displaced_header()方法备份了原对象的Mark Word.然后通过CAS,尝试将BasicLock的地址复制到对象头的Mark Word中。如果成功,则加锁成功,否则加锁失败。加锁失败可能会被膨胀为重量级锁。
锁膨胀->重量级锁
当轻量级锁失败,会膨胀为重量级锁.第1步是废弃前面BasicLock备份的对象头信息,第2步是通过inflate()方法进行锁膨胀,获取对象的ObjectMoniter,然后再通过enter()尝试进入该锁,在enter()方法中可能会在操作系统层面挂起线程,成本就会比较高。
lock->set_displaced_header(markOopDesc::unused_mark());ObjectSynchronizer::inflate(THREAD, obj()) -> enter(THREAD)
自旋锁
在锁膨胀后,在操作系统挂起线程之前,JVM会做最后一次争取避免被操作系统挂起,这种操作被称为自旋锁。
自旋锁可以使线程在没有取得锁时,不被挂起,而转为执行一个空循环,执行若干个循环后,能获取锁最好,不能则由操作系统挂起。
在JDK1.6时可以通过-XX:UseSpinning开启自旋锁,使用-XX:PreBlockSpin参数设定自旋锁等待次数。在JDK1.7中完全交给JVM,它自动执行,不能控制。
public class SpinLock { private AtomicReference<Thread> sign =new AtomicReference<>(); public void lock(){ Thread current = Thread.currentThread(); while(!sign .compareAndSet(null, current)){ } } public void unlock (){ Thread current = Thread.currentThread(); sign .compareAndSet(current, null); } }
锁消除
这是JVM在JIT编译阶段,通过上下文扫描,和逃逸分析,去除不可能出现竞态的锁。如像Vector,StringBuffer这些类。
参数:逃逸分析:+XX:+DoEscapeAnalysis,锁消除:+XX:EliminateLocks。必须在-server模式下才行。
其他措施
减小锁粒度:ConcurrentHashMap分了16段
锁分离:LinkedBlockingQueue有取放两把锁
锁粗化:减少获取锁的次数
CAS:Compare and Swap,atomic包
LongAddr:分段+CAS,更快的AtomicLong
volatile
保证了像Long,Double这种64位操作的原子性
保证了有序性,指令不会重排序
保证了可见性,强制CPU从内存读,而不是缓存
Happens-Before原则
指令重排序时候,不会违背这些原则
- 程序顺序原则:一个线程内保证语义的串行性
- volatile规则:volatile的写先于读,保证可见性
- 锁规则:解锁必须发生于加锁前
- 传递性:a先于b,b先于c,则a先于c
- 线程的start()先于它其他动作
- 线程所有动作先于线程的结束Thread.join()
- 线程的中断(interrupt())先于被中断的线程的代码
- 对象的constructor()早于finalize()
阅读全文
0 0
- 理解JVM(5)锁
- 深入理解JVM(5)
- jvm理解
- jvm理解
- jvm理解
- 理解JVM
- JVM理解
- JVM理解
- JVM理解
- jvm理解
- Jvm理解
- jvm理解
- 深入理解JVM 第一章 之5
- 【理解JVM】JVM内存模型
- 理解 JVM:JVM 内存模型
- jvm的理解
- 深入理解JVM
- 深入理解JVM
- Subway POJ
- 将项目上传到github
- Python数据可视化
- 刷一波数学期望,数论,数学
- 安装jdk
- 理解JVM(5)锁
- QtEmbedded-4.7.3-arm 移植、触摸屏移植、去掉鼠标箭头
- leveldb源码学习——系统函数封装Env
- Servlet学习笔记—ServletContext对象
- Similarity-Guided Streamline Placement with Error Evaluation
- 基于handsontable的web excel(下)
- csdn如何转载文章
- Image-Guided Streamline Placement
- 【Android系列】View的绘制之draw过程