java对象头与synchronized锁的升级过程
java对象头中都存了些什么?
32位jdk中:
锁状态 | 25bit | 4bit | 1bit | 2bit | |
23bit | 2bit | 偏向锁标志位(biased_lock) | 锁状态(lock) | ||
正常对象(normal object) | 对象hashcode(hash) | 对象分代年龄(age) | 0 | 01 | |
偏向锁(biased object) | 线程ID | Epoch | 对象分代年龄(age) | 1 | 01 |
轻量级锁 | 指向栈中锁记录指针 | 00 | |||
重量级锁 | 指向互斥量(重量级锁)的指针 | 10 | |||
GC标记 | 空 | 11 |
64位jdk中:
锁状态 | 25位 | 31位 | 1位 | 4位 | 1位 | 2位 | |
29位 | 2位 | 偏向锁标志位(biased_lock) | 锁状态(lock | ||||
正常对象(normal object) | 未使用 | 对象hashcode(hash) | 未使用 | 对象分代年龄(age) | 0 | 01 | |
偏向锁(biased object) | 线程ID | Epoch | 未使用 | 对象分代年龄(age) | 1 | 01 |
在javaSE1.6中,为了减少上下文切换带来的性能消耗,jdk引入了偏向锁与轻量锁
synchronized锁保证线程安全时锁的升级过程:
偏向锁:由于重量级锁在每次释放与获取锁时进行上下文切换对性能消耗大,而多数锁的获取与释放常常在同一个线程中进行,针对该现象引入了偏向锁进行优化;当一个线程访问同步代码块并获取锁时,会在对象头和栈帧中的所记录里存储偏向锁的线程ID,以后该线程在进入和退出同步代码块时,不用进行CAS操作来加锁解锁,只需要测试对象头的Mark World中是否存储着指向当前线程的偏向锁,如果测试成功,表示该线程已经获取到锁进行执行,如果测试失败,则需要再次测试Mark world中偏向锁标识位是否为1(表示位偏向锁),如果没有设置,则开始使用轻量级锁(CAS)进行竞争锁,如果是,则尝试使用CAS将对象头的偏向锁指向当前线程
偏向锁撤销:当有另一个线程进行竞争(竞争出现)这把锁时,才会撤销偏向锁,撤销的过程要等到全局安全点(在这个时间点上没有代码正在执行)才会进行,撤销前要西安暂停拥有偏向锁的线程,再检查该线程是否存活,如果不存活,则将对象头设位无所,否则拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中所记录和对象头的mark world要么偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程
偏向锁会在程序启动几秒后激活:可以通过-XX:BiasedLockingStartupDelay=0来消除这个延时
如果线程中的锁通常处于竞争状态,可以通过-XX:-UseBiasedKocking=false来关闭偏向锁
轻量级锁:
加锁:线程执行同步代码块前,jvm再当前线程的栈帧中创建用于存储所记录的空间,并将对象头中的mark word复制到所记录中,官方称为displaced mark word。然后线程尝试使用CAS将对象头的mark word替换为指向锁记录的指针。如果成功当前线程获取锁,否则其他线程获得锁,当前线程CAS等待
解锁:轻量级锁解锁时,会使用原子的CAS操作将Displaced mark word替换回对象头,如果成功,则表示没有竞争,如果失败,则表示该锁当前存在竞争,锁会升级为重量级锁
重量级锁:为了避免无用的自旋,一当锁升级为重量级锁,就不会恢复到轻量级锁,当锁为重量级锁的状态下,其他线程试图获取锁时,都将处于阻塞状态,当持有锁的线程释放锁时,会唤醒其他线程,进行新一轮锁的竞争