偏向锁
如果对象是刚创建的,那么是什么状态呢。在之前的输出可以找到,标志这baised_lock和lock是001,表示的是unlocked,也就是解锁状态。但是我们在文档Synchronization and Object Locking上的图可以看出,新生的对象状态会有两种可能。
开启偏向延迟的情况(默认)
如果偏向锁是开启的,那么对象状态应该是101,表示可偏向,如果偏向是关闭的,那么是001,表示不可偏向或者解锁。我这里使用的jdk8,默认是有开启偏向选择的,但是偏向的开启是有延迟的,默认是4s,-XX:+PrintFlagsFinal可以查看JVM默认值。-XX:BiasedLockingStartupDelay=0可以设置偏向延迟的时间.我们把它设置为0,再打印就能看见结果变为101,表示可偏向。但是由于没有记录线程ID,所以是匿名偏向的。同样也没有初始化hashcode。为了减少篇幅在接下来的打印中只保留mark word。 由于偏向锁是延迟开启的所以在默认情况下我们的对象可以分为两部分讨论,一部分在偏向锁延迟值内创建的对象,一部分在偏向锁开启时创建的对象。
例1: 这里我创建了三个对象,a,b,c,其中a,b在偏向锁延迟的时候创建,c在睡眠了4s等待偏向锁开启后创建。
//默认情况下的偏向锁
public class Test1 {
static class A{}
public static void main(String[] args) throws InterruptedException {
A a = new A();
A b = new A();
System.out.println("-----在偏向锁延迟的时间内------");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
synchronized (a){
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
Thread.sleep(4000);
System.out.println("-----在偏向锁延迟结束------");
A c = new A();
System.out.println(ClassLayout.parseInstance(c).toPrintable());
synchronized (c){
System.out.println(ClassLayout.parseInstance(c).toPrintable());
}
System.out.println("-----在偏向锁延迟结束后,查看在偏向锁延迟时间内创建的对象------");
System.out.println(ClassLayout.parseInstance(b).toPrintable());
}
}
//节约篇幅删除了部分输出
-----在偏向锁延迟的时间内------
Test1$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 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)
Test1$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 48 f4 dd 02 (01001000 11110100 11011101 00000010) (48100424)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
-----在偏向锁延迟结束------
Test1$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
Test1$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 38 34 03 (00000101 00111000 00110100 00000011) (53753861)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
-----在偏向锁延迟结束后,查看在偏向锁延迟时间内创建的对象------
Test1$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 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)
从输出结果可以看出,对象在对象延迟内创建的a对象,默认为不加锁(01),在执行同步代码块后直接膨胀为轻量级锁。而在偏向锁开启后创建的对象c,默认为匿名偏向(101),在执行同步代码块之后偏向了具体线程。最后在偏向锁延迟期间创建的对象b,在偏向锁开启之后并没有变成可偏向状态,仍未未加锁(01)。对象是否能够偏向是创建时决定的,对象创建完成之后就算开启了偏向锁也不能使对象的初始状态改变。
关闭偏向延迟的情况
既然明确了对象的初始状态,那么我们就来分析再单线程的情况下使用synchronized对象头会发生怎样的变化。
例2: 这里直接将偏向延迟设置为0来观察加锁前后对象mark word的变化。
//关闭了偏向锁延迟
public static void main(String[] args){
A a = new A();
System.out.println(ClassLayout.parseInstance(a).toPrintable());
synchronized (a){
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
//节约篇幅删除了部分输出
A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 38 d8 02 (00000101 00111000 11011000 00000010) (47724549)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 38 d8 02 (00000101 00111000 11011000 00000010) (47724549)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
在这段代码中,我们首先打印了a对象为101(表示匿名偏向,没有记录线程ID),在第二次打印时,我们锁住了对象a,所以就偏向了当前线程,也就是执行main方法的线程,并记录下了当前线程的ID(00000000 00000000 00000000 00000000 00000010 11011000 00111),此时a对象还是101(但是这时表示偏向main方法线程),最后我们在释放锁后,输出的对象a的头部信息仍为101(偏向main线程)。
也就是说如果偏向状态为1,那么表示当前的对象是允许偏向的,所以在执行sync同步代码块之后,会记录当前的线程Id,表示偏向这个线程,且同步代码块执行完之后,并没有清除线程的Id,仍然偏向记录ID的线程。
偏向锁加锁源码分析
这个是字节码解析器的类,当我们的偏向锁加锁的的时候会使用到其中的一些属性。 其中_monitor_base表示本地偏向锁栈的栈底对象。_stack_base和_stack_limit表示该栈的栈底指针和头指针。当一个对象尝试加锁的时候,会从栈底到栈顶遍历,在最靠近栈顶的位置找一个可用的BasicObjectLock。
class BytecodeInterpreter : StackObj {
private:
JavaThread* _thread; // the vm's java thread pointer
address _bcp; // instruction pointer
intptr_t* _locals; // local variable pointer
ConstantPoolCache* _constants; // constant pool cache
DataLayout* _mdx; // compiler profiling data for current bytecode
intptr_t* _stack; // expression stack
messages _msg; // frame manager <-> interpreter message
frame_manager_message _result; // result to frame manager
interpreterState _prev_link; // previous interpreter state
oop _oop_temp; // mirror for interpreted native, null otherwise
intptr_t* _stack_base; // base of expression stack
intptr_t* _stack_limit; // limit of expression stack
BasicObjectLock* _monitor_base; // base of monitors on the native stack
什么叫做可用的BasicObjectLock呢,如果该BasicObjectLock的_obj为空,也就是说没有指向具体的java对象,就说明是未被使用的。在加锁的时候会将其指向具体的对象,在解锁的时候优惠将它重置为NULL.其中_lock具体的偏向锁对象。
//在全局中的定义
#define VALUE_OBJ_CLASS_SPEC : public _ValueObj
class BasicObjectLock VALUE_OBJ_CLASS_SPEC {
friend class VMStructs;
private:
BasicLock _lock; // the lock, must be double word aligned
oop _obj; // object holds the lock;
public:
// Manipulation
oop obj() const { return _obj; }
void set_obj(oop obj) { _obj = obj; }
BasicLock* lock() { return &_lock; }
};
在偏向锁BasicLock中保存了markOop,也就是对象头中mark word的部分。
class BasicLock VALUE_OBJ_CLASS_SPEC {
friend class VMStructs;
private:
volatile markOop _displaced_header;
public:
markOop displaced_header() const { return _displaced_header; }
void set_displaced_header(markOop header) { _displaced_header = header; }
void print_on(outputStream* st) const;
// move a basic lock (used during deoptimization
void move_to(oop obj, BasicLock* dest);
static int displaced_header_offset_in_bytes() { return offset_of(BasicLock, _displaced_header); }
};
这里是一个switch case 分支,当我们执行sync代码块时,会走到这个加锁分支.
CASE(_monitorenter): {
//这是当前对象的对象头
oop lockee = STACK_OBJECT(-1);
//不为空检查
CHECK_NULL(lockee);
//这里是为了找到一个可用的空闲基础锁
//istate指代当前的BytecodeInterpreter对象,这里取到_monitor_base对象,也就是基础锁的栈底指针
BasicObjectLock* limit = istate->monitor_base();
//获取表达式栈的栈底的地址
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
//entry用来指代该对象将要获得的BasicObjectLock
BasicObjectLock* entry = NULL;
//从栈底到栈顶遍历本地基础锁的栈
while (most_recent != limit ) {
//如果if为true表示找到了一个空闲的基础锁,但是不跳出循环,而是继续循环,使分配到的BasicObjectLock尽量的靠近栈顶
if (most_recent->obj() == NULL) entry = most_recent;
//如果找到该对象已经持有的基础锁,则直接跳出
//注意这里并没有替换entry,而是直接跳出了。也就是说如果之前该对象已经持有一个BasicObjectLock,那么还会再分配一个。
else if (most_recent->obj() == lockee) break;
most_recent++;
}
if (entry != NULL) {
//如果entry不为空说明找到了一个空闲的基础锁
//将指针指向当前的对象
entry->set_obj(lockee);
//success表示偏向锁是否成功,默认失败
int success = false;
//获取到epoch的位置
uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place;
//该对象自己的mark word
markOop mark = lockee->mark();
//no_hash是markOopDesc中的一个枚举,标号是0
intptr_t hash = (intptr_t) markOopDesc::no_hash;
// 判断该对象是否能够偏向
if (mark->has_bias_pattern()) {
uintptr_t thread_ident;
uintptr_t anticipated_bias_locking_value;
//vm线程的指针
thread_ident = (uintptr_t)istate->thread();
anticipated_bias_locking_value =
(((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
~((uintptr_t) markOopDesc::age_mask_in_place);
//anticipated_bias_locking_value == 0代表偏向的线程是当前线程,且mark word的epoch等于class的epoch
if (anticipated_bias_locking_value == 0) {
//这个是PrintBiasedLockingStatistics是输出全局统计锁信息的开关,如果配置为true,则输出相关的统计信息,下同
if (PrintBiasedLockingStatistics) {
//统计计数器中的偏向锁计数器+1
(* BiasedLocking::biased_lock_entry_count_addr())++;
}
success = true;
//如果该偏向锁是重入的,也就是之前已经分配了一个BasicObjectLock,那么执行到这里就结束了。
//该对象有几个BasicObjectLock,就表示重入了几次。
}
else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
//如果偏向模式关闭,则尝试撤销偏向锁
//获取该对象类的mark word对象
markOop header = lockee->klass()->prototype_header();
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
// CAS将对象的mark word替换为class中的mark word
if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
//统计计数器中的撤销偏向锁的计数器+1
(*BiasedLocking::revoked_lock_entry_count_addr())++;
}
}
else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
// 尝试重偏向
//构造一个偏向当前线程的mark word
markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident);
if (hash != markOopDesc::no_hash) {
new_header = new_header->copy_set_hash(hash);
}
//CAS替换对象的mark word
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark) == mark) {
if (PrintBiasedLockingStatistics)
//统计计数器中的重偏向计数器+1
(* BiasedLocking::rebiased_lock_entry_count_addr())++;
}
else {
//如果cas失败了表示有竞争,锁升级
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
else {
// 剩下匿名偏向的情况,和已经偏向的情况
//这时候entry不但_obj会指向当前对象头,而且其中的BasicLock中的displaced_header也会指向当前对象头。
//重入的话就只设置_obj
//计算新的匿名的 mark word
markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |
(uintptr_t)markOopDesc::age_mask_in_place |
epoch_mask_in_place));
if (hash != markOopDesc::no_hash) {
header = header->copy_set_hash(hash);
}
//记录偏向的线程
markOop new_header = (markOop) ((uintptr_t) header | thread_ident);
// debugging hint
DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);)
//cas 替换原来的对象头,如果已经偏向了其他的线程,那么和预期值不符,就会失败进入else
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
if (PrintBiasedLockingStatistics)
//统计计数器中的匿名偏向计数器+1
(* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
}
else {
//说明存在竞争,锁升级
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
}
if (!success) {
//不是偏向锁的逻辑,偏向标志被关闭了
//创建一个无锁的mark word
markOop displaced = lockee->mark()->set_unlocked();
//让指向当前对象的BasicObjectLock记录该mark word
entry->lock()->set_displaced_header(displaced);
//UseHeavyMonitors是虚拟机可配置项,表示是否直接开启重量级锁,并禁用偏向和轻量锁
bool call_vm = UseHeavyMonitors;
//如果UseHeavyMonitors是开启的,不执行cas,否则执行。成功进入内层if,失败不处理
if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
//这里是UseHeavyMonitors开启,或者这里是UseHeavyMonitors关闭,但cas成功
//如果是后者,再次判断当前锁的是否是当前线程,也就是是否重入
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
//set_displaced_h是用来设置轻量级锁的锁头
entry->lock()->set_displaced_header(NULL);
} else {
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
}
}
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
} else {
istate->set_msg(more_monitors);
UPDATE_PC_AND_RETURN(0); // Re-execute
}
}
偏向锁释放源码分析
当同步代码块中的语句都运行完后,会执行锁的释放。这里是switch case中锁释放逻辑。
CASE(_monitorexit): {
//获取当前对象
oop lockee = STACK_OBJECT(-1);
//不为空检查
CHECK_NULL(lockee);
// derefing's lockee ought to provoke implicit null check
// find our monitor slot
BasicObjectLock* limit = istate->monitor_base();
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
//从栈底遍历到栈顶
while (most_recent != limit ) {
//找到当前对象的BasicObjectLock
if ((most_recent)->obj() == lockee) {
//获取到对象的偏向锁
BasicLock* lock = most_recent->lock();
//拿到该对象的mark word
markOop header = lock->displaced_header();
//将BasicObjectLock中的_obj设置为空,这就相当于释放了锁
most_recent->set_obj(NULL);
//如果该对象的mark word 不是偏向模式,也就是说是轻量级锁或者重量级锁
if (!lockee->mark()->has_bias_pattern()) {
bool call_vm = UseHeavyMonitors;
// If it isn't recursive we either must swap old header or call the runtime
if (header != NULL || call_vm) {
if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
// restore object for the slow case
most_recent->set_obj(lockee);
CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);
}
}
}
UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);
}
most_recent++;
}
// Need to throw illegal monitor state exception
CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception);
ShouldNotReachHere();
}
单个锁的重偏向?
严格来说,单个锁是不能进行重偏向操作的,但是我们可能会发现下面这种的情况。
例3: 这里创建了顺序执行了两个线程,并打印在同步代码块中对象a 的mark word 信息。
//关闭了偏向延迟
public class Test1 {
static class A{}
public static void main(String[] args) throws InterruptedException {
A a = new A();
class MyThread extends Thread{
@Override
public void run() {
synchronized (a){
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
MyThread t1 = new MyThread();
t1.start();
t1.join();
MyThread t2 = new MyThread();
t2.start();
t2.join();
}
}
//节约篇幅删除了部分输出
Test1$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 80 d2 1d (00000101 10000000 11010010 00011101) (500334597)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
Test1$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 80 d2 1d (00000101 10000000 11010010 00011101) (500334597)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
这里我创建了两个线程t1和t2,在t1执行同步块后,对象a偏向t1线程。在t1线程运行结束后,t2线程也来执行同步代码块,发现仍然是偏向锁(101),这与网上的一些资料有着明显的冲突。**这时候不是应该膨胀为轻量级锁吗,怎么还是偏向锁呢?**我们可以发现当前的线程Id被复用了。
我们可以简单推测一下这段代码的流程,首先我们创建了一个一个对象a,由于关闭了偏向延迟,所以a的初始状态是匿名偏向的。接着t1来竞争锁,于是a偏向了t1。之后t1运行结束并死亡(或者并没有死亡,被直接复用了,但是操作系统的线程和java中的线程是一一对应的,所以这种情况排除),这时创建了t2线程,但是线程t2复用了线程t1的ID(这里有两种可能,一个是jvm复用了线程Id,另一种是jvm直接拿了OS的线程ID,OS复用了线程ID,但是我在win和linux都测试了代码,结果是相同的,而且OS的线程ID格式与jvm的也不相同,所以排除后者),由于偏向锁在退出同步代码块后并没有清除偏向的线程ID T1,所以sync还会认为t2线程就是线程t1,于是不会进行锁的升级。
推测结论:jvm会复用线程的ID,如果对象偏向了某个线程t1,而t1运行结束后的Id恰好被线程t2复用了,由于mark word保存了t1 的Id,所以synchronized会认为t2线程就是t1线程,于是不会进行锁升级。
例4: 我们在t1和t2之间插入了一个线程temp,让temp复用t1的线程ID,再让t2运行。(为了防止temp运行完线程再次被t2复用,这里让temp睡眠较长的时间,temp中的对象b仅用来辅助查看temp的线程ID)
//关闭了偏向延迟
public static void main(String[] args) throws InterruptedException {
A a = new A();
class MyThread extends Thread{
@Override
public void run() {
synchronized (a){
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
MyThread t1 = new MyThread();
t1.start();
t1.join();
Thread temp = new Thread(){
@Override
public void run() {
A b = new A();
synchronized (b){
try {
System.out.println(ClassLayout.parseInstance(b).toPrintable());
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
temp.start();
MyThread t2 = new MyThread();
t2.start();
t2.join();
}
//节约篇幅删除了部分输出
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b0 1b 1e (00000101 10110000 00011011 00011110) (505131013)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b0 1b 1e (00000101 10110000 00011011 00011110) (505131013)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 90 f1 d1 1e (10010000 11110001 11010001 00011110) (517075344)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
结果可以看出temp复用了线程t1的ID,然后t2使用了新的线程Id,这种情况下对象a就膨胀为轻量级锁(00)了。(当然也有可能temp创建了新的线程ID,t2还是拿到t1的Id。结果不是唯一的。)
锁的批量重偏向
一个类A的一组对象a1..an已经分别偏向了某些个线程,新线程的竞争会使a1...an撤销偏向变成轻量级锁,当这个撤销次数到达某一阈值时(默认20),对象会直接偏向当前线程,而不是变成轻量级锁。-XX:BiasedLockingBulkRebiasThreshold=20 可以设置偏向锁重偏向的阈值,默认为20。
例5: 这里我创建了30个对象存放在list中,并且这些对象都偏向了main线程。可以发现a0-a18这19个对象都膨胀成轻量级锁,而在a19-a25之后就直接重偏向t1线程。epoch也从00变成01。没有进行重偏向的对象mark word保持不变(前后比较a28的mark word没有变化)。
//关闭了偏向锁延迟
public static void main(String[] args) throws InterruptedException {
List<A> list = new ArrayList();
for(int i = 0;i<30;i++){
A a = new A();
synchronized (a){
}
list.add(a);
}
System.out.println("a0");
System.out.println(ClassLayout.parseInstance(list.get(0)).toPrintable());
System.out.println("a28");
System.out.println(ClassLayout.parseInstance(list.get(28)).toPrintable());
Thread t1 = new Thread(){
@Override
public void run() {
for(int i = 0;i< 25;i++){
A a = list.get(i);
synchronized (a){
System.out.println("a" + i);
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
};
t1.start();
t1.join();
System.out.println("a28");
System.out.println(ClassLayout.parseInstance(list.get(28)).toPrintable());
System.out.println("new a");
System.out.println(ClassLayout.parseInstance(new A()).toPrintable());
}
//节约篇幅删除了部分输出
a0
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 38 e3 02 (00000101 00111000 11100011 00000010) (48445445)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
a28
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 38 e3 02 (00000101 00111000 11100011 00000010) (48445445)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
//省略a0-a17的输出
a18
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 70 f4 d9 1e (01110000 11110100 11011001 00011110) (517600368)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
Total time for which application threads were stopped: 0.0000694 seconds, Stopping threads took: 0.0000351 seconds
a19
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 d9 9c 1d (00000101 11011001 10011100 00011101) (496818437)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
//省略a20-a24的输出
a28
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 38 e3 02 (00000101 00111000 11100011 00000010) (48445445)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
new a
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 01 00 00 (00000101 00000001 00000000 00000000) (261)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
上述的例子中,批量重偏向并不是显示的执行的,而是通过改变类中的epoch值。间接的使对象对的epoch过期。新线程来获取锁时,会通过比较两个epoch来决定是否进行重偏向。所以在例1中,a28并没有新的线程来尝试获取锁,也就没有比较类A和a28的epoch值,所以没有重新加锁的对象整个mark word都不会改变。 在触发了批量重偏向之后,类中的epoch改变,那么新生的对象的epoch会跟随类中的epoch,所以new a 对象的epoch是01
例6: 这里我创建了100个对象,这些对象全都偏向了main。不同的是,我用循环创建了10个线程,每个线程竞争10个对象,并打印信息。这里的temp线程是为例防止线程id的复用。在偏向锁撤销了20次之后,都是直接偏向了新的线程。
//关闭了偏向延迟
public class Test {
static class A {}
public static void main(String[] args) throws InterruptedException {
List<A> list = new ArrayList();
for(int i = 0;i<100;i++){
A a = new A();
synchronized (a){
}
list.add(a);
}
for(int i = 0;i< 10;i++){
int begin = i*10;
int end = (i+1)*10;
Thread t = new Thread(){
@Override
public void run() {
for(int i = begin;i< end;i++){
A a = list.get(i);
synchronized (a){
System.out.println(Thread.currentThread().getName() + " a" + i);
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
};
t.setName("t"+i);
t.start();
t.join();
System.out.println("------------------------------");
Thread temp = new Thread(){
@Override
public void run() {
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
temp.start();
}
}
}
//节约篇幅删除了部分输出
t0 a1
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 40 f2 9f 1e (01000000 11110010 10011111 00011110) (513798720)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
------------------------------
t1 a10
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 10 f6 2f 1f (00010000 11110110 00101111 00011111) (523236880)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
t1 a18
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 10 f6 2f 1f (00010000 11110110 00101111 00011111) (523236880)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
t1 a19
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 31 05 1e (00000101 00110001 00000101 00011110) (503656709)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
------------------------------
t2 a20
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 69 bd 1e (00000101 01101001 10111101 00011110) (515729669)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
//省略
------------------------------
t3 a30
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e1 bc 1e (00000101 11100001 10111100 00011110) (515694853)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
//省略
------------------------------
t4 a40
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 59 bc 1e (00000101 01011001 10111100 00011110) (515660037)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
//省略
------------------------------
t5 a50
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b9 bd 1e (00000101 10111001 10111101 00011110) (515750149)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
//以下省略
这个例子是模拟了多个线程的情况,在触发了批量重偏向之后,只要对象是可偏向状态,那么就会重偏向新的线程,并不是局限只能重偏向固定一个或两个线程。偏向锁的触发条件是偏向锁被撤销的次数,与具体是哪个线程触发了批量重偏向是无关的,同样在重偏向开启后,只要对象的epoch过期了,就会偏向新的线程。(当然对象还必须是可偏向的)
例7: 这个例子和例5类似,区别是增加了一个lock线程来观察正在执行同步代码块时,当触发了批量重偏向,对象locking的mark word的变化,这里省略了其他对象的输出。
//关闭了偏向锁延迟
public static void main(String[] args) throws InterruptedException {
A locking = new A();
System.out.println("before locking");
System.out.println(ClassLayout.parseInstance(locking).toPrintable());
Thread lock = new Thread(){
@Override
public void run() {
synchronized (locking){
try {
System.out.println("locking");
System.out.println(ClassLayout.parseInstance(locking).toPrintable());
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after locking");
System.out.println(ClassLayout.parseInstance(locking).toPrintable());
}
}
};
lock.start();
List<A> list = new ArrayList();
for(int i = 0;i<30;i++){
A a = new A();
synchronized (a){
}
list.add(a);
}
//确保lock线程打印了locking状态的mark word
Thread.sleep(1000);
Thread t1 = new Thread(){
@Override
public void run() {
for(int i = 0;i< 25;i++){
A a = list.get(i);
synchronized (a){
}
}
}
};
t1.start();
t1.join();
}
before locking
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
locking
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 41 42 1e (00000101 01000000 01000010 00011110) (507658501)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
after locking
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 41 42 1e (00000101 01000001 01000010 00011110) (507658501)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
从结果可以看出,locking对象在退出同步代码块之后,epoch值从00直接变成了01,而不是保持不变。原因是当触发批量重偏向时jvm线程会在线程安全点遍历所有线程的栈,找到正在处于锁定状态的对象,并直接修改epoch,那么这个对象的epoch就和该类中的epoch一样了,这个对象也就不能重偏向了。
值得注意的是,类中保存的对象偏向锁撤销次数并不是无限增加的,上限为批量撤销的阈值(默认40),也就是说,当你批量撤销的阈值设置的比重偏向的阈值小的时候,将无法触发重偏向。
锁的批量撤销
当已经重偏向的对象,再次撤销偏向,达到阈值(40)时,会触发锁的批量撤销。这时锁将不会进行重偏向,这个新创建的对象也不允许偏向。-XX:BiasedLockingBulkRevokeThreshold可以设置批量撤销的阈值。
例8: 这里创建了100个对象,并偏向main线程。t1给对象a0-a59重新加锁后,使a19-a59重新偏向t1。这时t2对a30-a49(20个对象)重新加锁,使它们又撤销偏向(加上a0-a19的20个对象,共40个对象撤销了偏向),达到了批量撤销的阈值。这时再使用t3对a60-a69加锁,对象也不会偏向t3,而且新建的a对象也是不能偏向的。
//关闭了偏向延迟
public class Test {
static class A {}
public static void main(String[] args) throws InterruptedException {
List<A> list = new ArrayList();
for(int i = 0;i<100;i++){
A a = new A();
synchronized (a){
}
list.add(a);
}
Thread t1 = new Thread(){
@Override
public void run() {
for(int i = 0;i< 60;i++){
A a = list.get(i);
synchronized (a){
System.out.println("a" + i);
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
};
t1.start();
t1.join();
Thread temp1 = new Thread(){
@Override
public void run() {
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
temp1.start();
System.out.println("------------------------------");
Thread t2 = new Thread(){
@Override
public void run() {
for(int i = 30;i< 50;i++){
A a = list.get(i);
synchronized (a){
System.out.println("a" + i);
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
// if(i == 40){
// try {
// Thread.sleep(25000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
}
}
};
t2.start();
t2.join();
Thread temp2 = new Thread(){
@Override
public void run() {
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
temp2.start();
System.out.println("------------------------------");
Thread t3 = new Thread(){
@Override
public void run() {
for(int i = 60;i< 70;i++){
A a = list.get(i);
synchronized (a){
System.out.println("a" + i);
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
}
};
t3.start();
t3.join();
System.out.println("a");
System.out.println(ClassLayout.parseInstance(new A()).toPrintable());
}
}
a18
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 40 ed ed 1d (01000000 11101101 11101101 00011101) (502132032)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
a19
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 09 39 1d (00000101 00001001 00111001 00011101) (490277125)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
------------------------------
a0
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) d0 f1 ed 1d (11010000 11110001 11101101 00011101) (502133200)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
------------------------------
a60
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 40 f0 8d 1e (01000000 11110000 10001101 00011110) (512618560)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
a
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 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)
锁的偏向次数是会被重置的,如果当前类重偏向次数大于批量偏向,且小于批量撤销的次数(20<=n<40),而且当前时间与最后一次偏向撤销的间隔时间过长(默认2500)那么类中记录的偏向撤销会被重置为0,接着偏向次数+1,可以简单理解重置为1。参数BiasedLockingDecayTime可以设置这个时间。
也就是说,在上面这段代码中将t2 中执行if代码块,那么批量撤销不会生效,输出的对象的mark word全部都是偏向锁。
例9: 这个例子也是增加了一个lock线程来观察正在执行同步代码块时,当触发了批量撤销,对象locking的mark word的变化,这里省略了其他对象的输出。
//关闭了偏向锁延迟
public class Test {
static class A {}
public static void main(String[] args) throws InterruptedException {
A locking = new A();
System.out.println("before locking");
System.out.println(ClassLayout.parseInstance(locking).toPrintable());
Thread lock = new Thread(){
@Override
public void run() {
synchronized (locking){
try {
System.out.println("locking");
System.out.println(ClassLayout.parseInstance(locking).toPrintable());
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after locking");
System.out.println(ClassLayout.parseInstance(locking).toPrintable());
}
}
};
lock.start();
List<A> list = new ArrayList();
for(int i = 0;i<100;i++){
A a = new A();
synchronized (a){
}
list.add(a);
}
Thread t1 = new Thread(){
@Override
public void run() {
for(int i = 0;i< 60;i++){
A a = list.get(i);
synchronized (a){
}
}
}
};
t1.start();
t1.join();
Thread temp1 = new Thread(){
@Override
public void run() {
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
temp1.start();
//确保locking已经打印
Thread.sleep(1000);
Thread t2 = new Thread(){
@Override
public void run() {
for(int i = 30;i< 50;i++){
A a = list.get(i);
synchronized (a){
}
}
}
};
t2.start();
t2.join();
}
}
before locking
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
locking
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b9 f8 1d (00000101 10111001 11111000 00011101) (502839557)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
after locking
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) d8 f1 e7 1d (11011000 11110001 11100111 00011101) (501739992)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
从结果可以看出,locking对象在退出同步代码块之后,由偏向锁状态直接改编成了轻量锁。原因是当触发批量撤销时jvm线程会在线程安全点遍历所有线程的栈,找到正在处于锁定状态的对象,并直接将其取消偏向,升级为轻量级锁。
批量重偏向和批量撤销原理
// Klass layout:
// [C++ vtbl ptr ] (contained in Metadata)
// [layout_helper ]
// [super_check_offset ] for fast subtype checks
// [name ]
// [secondary_super_cache] for fast subtype checks
// [secondary_supers ] array of 2ndary supertypes
// [primary_supers 0]
// [primary_supers 1]
// [primary_supers 2]
// ...
// [primary_supers 7]
// [java_mirror ]
// [super ]
// [subklass ] first subclass
// [next_sibling ] link to chain additional subklasses
// [next_link ]
// [class_loader_data]
// [modifier_flags]
// [access_flags ]
[last_biased_lock_bulk_revocation_time] (64 bits)
[prototype_header]
[biased_lock_revocation_count]
// [_modified_oops]
// [_accumulated_modified_oops]
// [trace_id]
每个类保存一个偏向锁的计数器(biased_lock_revocation_count),每次这个类的对象发生偏向撤销时,该计数器+1,并记录最后一次撤销偏向的时间(last_biased_lock_bulk_revocation_time)。prototype_header指向了对象markOop,也就是开头展示的保存mark word的对象。当处于偏向锁状态时,对象会有一个对应的epoch字段,同样的该类的class对象中prototype_header也有一个相应的字段。新对象的epoch值是根据类中的epoch复制而来。当批量重偏向触发时,类中epoch值+1,这时已经已经创建的对象的epoch就和类中epoch不相同,也就是失效了。如果尝试对已经失效的对象加锁,就会更具类中的mark word做出相应的反应。
如果类中的偏向状态时开启的,那么就会偏向新的线程。如果类中偏向状态太被关闭了,那么就会升级为轻量级锁。
void BiasedLocking::revoke_at_safepoint(GrowableArray<Handle>* objs) {
assert(SafepointSynchronize::is_at_safepoint(), "must only be called while at safepoint");
int len = objs->length();
for (int i = 0; i < len; i++) {
oop obj = (objs->at(i))();
//这个方法会根据偏向锁已经撤销的次数来返回是否进行批量冲偏向或者批量撤销
HeuristicsResult heuristics = update_heuristics(obj, false);
//单个锁的撤销
if (heuristics == HR_SINGLE_REVOKE) {
revoke_bias(obj, false, false, NULL, NULL);
} else if ((heuristics == HR_BULK_REBIAS) ||
(heuristics == HR_BULK_REVOKE)) {
//批量撤销或者批量重偏向
bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);
}
}
clean_up_cached_monitor_info();
}
// 这个方法如果返回HR_BULK_REBIAS 或者 HR_BULK_REVOKE 就会执行批量撤销或者批量重偏向
static HeuristicsResult update_heuristics(oop o, bool allow_rebias) {
//获取对象的mark word
markOop mark = o->mark();
//判断是否是偏向模式
if (!mark->has_bias_pattern()) {
//返回不是偏向标识
return HR_NOT_BIASED;
}
// 获取该对象的类对象
Klass* k = o->klass();
// 获取当前的时间
jlong cur_time = os::javaTimeMillis();
// 获取该类上一次批量撤销的时间
jlong last_bulk_revocation_time = k->last_biased_lock_bulk_revocation_time();
// 获取该类偏向锁撤销的次数
int revocation_count = k->biased_lock_revocation_count();
// 如果已撤销的次数大于批量重偏向的次数,并小于批量撤销的次数
//并且撤销时间不为0,也就是被初始化过,并且时间间隔大于重置的阈值
if ((revocation_count >= BiasedLockingBulkRebiasThreshold) &&
(revocation_count < BiasedLockingBulkRevokeThreshold) &&
(last_bulk_revocation_time != 0) &&
(cur_time - last_bulk_revocation_time >= BiasedLockingDecayTime)) {
//将撤销的次数重置为0
k->set_biased_lock_revocation_count(0);
revocation_count = 0;
}
// 如果撤销的次数小于批量撤销次数,+1
if (revocation_count <= BiasedLockingBulkRevokeThreshold) {
revocation_count = k->atomic_incr_biased_lock_revocation_count();
}
// 如果达到批量撤销阈值则返回相应标识
if (revocation_count == BiasedLockingBulkRevokeThreshold) {
return HR_BULK_REVOKE;
}
// 如果达到批量重偏向阈值则返回相应标识
if (revocation_count == BiasedLockingBulkRebiasThreshold) {
return HR_BULK_REBIAS;
}
// 返回撤销单个对象的锁标识
return HR_SINGLE_REVOKE;
}
//批量撤销和批量重偏向,此方法必须在安全点执行
static BiasedLocking::Condition bulk_revoke_or_rebias_at_safepoint(oop o,
bool bulk_rebias,
bool attempt_rebias_of_object,
JavaThread* requesting_thread) {
assert(SafepointSynchronize::is_at_safepoint(), "must be done at safepoint");
//jvm可配置参数 是否打印偏向锁的记录信息
if (TraceBiasedLocking) {
tty->print_cr("* Beginning bulk revocation (kind == %s) because of object "
INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(bulk_rebias ? "rebias" : "revoke"),
p2i((void *) o), (intptr_t) o->mark(), o->klass()->external_name());
}
//获取当前时间
jlong cur_time = os::javaTimeMillis();
//设置当前类的最后一次撤销偏向锁的时间
o->klass()->set_last_biased_lock_bulk_revocation_time(cur_time);
//获取对象的类
Klass* k_o = o->klass();
Klass* klass = k_o;
//如果需要执行批量重偏向
if (bulk_rebias) {
//判断当前的类是否有偏向的标识
if (klass->prototype_header()->has_bias_pattern()) {
//获取当前类的epoch值
int prev_epoch = klass->prototype_header()->bias_epoch();
//将类的epoch值+1
klass->set_prototype_header(klass->prototype_header()->incr_bias_epoch());
// 获取改变后的epoch
int cur_epoch = klass->prototype_header()->bias_epoch();
//遍历所有线程的栈
for (JavaThread* thr = Threads::first(); thr != NULL; thr = thr->next()) {
//返回锁定在此线程上的所有对象的监控信息,从最小到最老的顺序
GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(thr);
//遍历锁定的对象
for (int i = 0; i < cached_monitor_info->length(); i++) {
//获取到该对象的监视器信息
MonitorInfo* mon_info = cached_monitor_info->at(i);
//拿到该对象的对象头
oop owner = mon_info->owner();
//拿到对象头的mark word部分
markOop mark = owner->mark();
//判断这个锁定的对象是否属于当前的类,且是否可偏向
if ((owner->klass() == k_o) && mark->has_bias_pattern()) {
// We might have encountered this object already in the case of recursive locking
assert(mark->bias_epoch() == prev_epoch || mark->bias_epoch() == cur_epoch, "error in bias epoch adjustment");
//如果满足条件,就更新epoch为类的epoch
owner->set_mark(mark->set_bias_epoch(cur_epoch));
}
}
}
}
//当前的这个对象撤销偏向
revoke_bias(o, attempt_rebias_of_object && klass->prototype_header()->has_bias_pattern(), true, requesting_thread, NULL);
} else {
//这里是批量撤销,逻辑和批量重偏向类似
if (TraceBiasedLocking) {
ResourceMark rm;
tty->print_cr("* Disabling biased locking for type %s", klass->external_name());
}
//将类的头设置为不可偏向
klass->set_prototype_header(markOopDesc::prototype());
//遍历线程的栈
for (JavaThread* thr = Threads::first(); thr != NULL; thr = thr->next()) {
//返回锁定在此线程上的所有对象的监控信息,从最小到最老的顺序
GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(thr);
//获取到该对象的监视器信息
for (int i = 0; i < cached_monitor_info->length(); i++) {
//获取到该对象的监视器信息
MonitorInfo* mon_info = cached_monitor_info->at(i);
//拿到该对象的对象头
oop owner = mon_info->owner();
//拿到对象头的mark word部分
markOop mark = owner->mark();
if ((owner->klass() == k_o) && mark->has_bias_pattern()) {
//撤销这个对象撤销偏向
revoke_bias(owner, false, true, requesting_thread, NULL);
}
}
}
//当前的这个对象撤销偏向
revoke_bias(o, false, true, requesting_thread, NULL);
}
if (TraceBiasedLocking) {
tty->print_cr("* Ending bulk revocation");
}
BiasedLocking::Condition status_code = BiasedLocking::BIAS_REVOKED;
if (attempt_rebias_of_object &&
o->mark()->has_bias_pattern() &&
klass->prototype_header()->has_bias_pattern()) {
markOop new_mark = markOopDesc::encode(requesting_thread, o->mark()->age(),
klass->prototype_header()->bias_epoch());
o->set_mark(new_mark);
status_code = BiasedLocking::BIAS_REVOKED_AND_REBIASED;
if (TraceBiasedLocking) {
tty->print_cr(" Rebiased object toward thread " INTPTR_FORMAT, (intptr_t) requesting_thread);
}
}
assert(!o->mark()->has_bias_pattern() ||
(attempt_rebias_of_object && (o->mark()->biased_locker() == requesting_thread)),
"bug in bulk bias revocation");
return status_code;
}
// MonitorInfo对象部分信息
class MonitorInfo : public ResourceObj {
private:
oop _owner; // 该对象的头部信息
BasicLock* _lock;
oop _owner_klass; // 该对象类的头部信息
bool _eliminated;
bool _owner_is_scalar_replaced;
对象的hashcode
这里值得注意的一点是,markOop.hpp的注释中可以看出,hashcode和线程ID是公用一个位置的,那么如果我们初始化了hashcode,线程信息该怎么存放呢?
例10: 我们这里做了一个测试,先打印对象a的信息,在初始化后再次打印了a的信息,最后进行加锁过程中和解锁后再次打印,共打印了4次a的信息。
//关闭了偏向延迟
public static void main(String[] args){
A a = new A();
System.out.println(ClassLayout.parseInstance(a).toPrintable());
System.out.println(Integer.toHexString(a.hashCode()));
System.out.println(ClassLayout.parseInstance(a).toPrintable());
synchronized (a){
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
//节约篇幅删除了部分输出
A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
deb6432
A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 32 64 eb (00000001 00110010 01100100 11101011) (-345755135)
4 4 (object header) 0d 00 00 00 (00001101 00000000 00000000 00000000) (13)
A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) f8 f5 93 02 (11111000 11110101 10010011 00000010) (43251192)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 32 64 eb (00000001 00110010 01100100 11101011) (-345755135)
4 4 (object header) 0d 00 00 00 (00001101 00000000 00000000 00000000) (13)
这里我们第一次打印101(可偏向),但是在打印了hashcode后就变成了001(不可偏向),那么不可偏向的对象在加锁后就变成了000(轻量级锁)而不是偏向锁,最后在执行完synchronized代码块后又恢复称001(解锁且不可偏向)的状态。所以说初始化过hashcode的对象不会有偏向锁这个状态,而是直接跳到轻量级锁。
例11: 与例10类似,区别在于把hashcode的计算放在了同步代码块中.
//关闭了偏向锁延迟
public class Test {
static class A {}
public static void main(String[] args){
A a = new A();
synchronized (a){
System.out.println(Integer.toHexString(a.hashCode()));
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
330bedb4
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 5a a3 a6 02 (01011010 10100011 10100110 00000010) (44475226)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
test.Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 5a a3 a6 02 (01011010 10100011 10100110 00000010) (44475226)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
可以看出这里锁直接膨胀为重量级锁。
锁与hashcode关系源码分析
//对象调用这个方法生成hashcode
intptr_t ObjectSynchronizer::FastHashCode (Thread * Self, oop obj) {
//jvm可配置参数是否开启了偏向锁
if (UseBiasedLocking) {
//如果对象能够偏向
if (obj->mark()->has_bias_pattern()) {
// Box and unbox the raw reference just in case we cause a STW safepoint.
Handle hobj (Self, obj) ;
// Relaxing assertion for bug 6320749.
assert (Universe::verify_in_progress() ||
!SafepointSynchronize::is_at_safepoint(),
"biases should not be seen by VM thread here");
//撤销该对象的偏向
BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());
obj = hobj() ;
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
}
// hashCode() is a heap mutator ...
// Relaxing assertion for bug 6320749.
assert (Universe::verify_in_progress() ||
!SafepointSynchronize::is_at_safepoint(), "invariant") ;
assert (Universe::verify_in_progress() ||
Self->is_Java_thread() , "invariant") ;
assert (Universe::verify_in_progress() ||
((JavaThread *)Self)->thread_state() != _thread_blocked, "invariant") ;
ObjectMonitor* monitor = NULL;
markOop temp, test;
intptr_t hash;
markOop mark = ReadStableMark (obj);
// object should remain ineligible for biased locking
assert (!mark->has_bias_pattern(), "invariant") ;
//代码运行到这对象的偏向就已经被取消了,所以锁至少是轻量级的
//是否为无锁状态001
if (mark->is_neutral()) {
//判断hashcode是否被初始化过,如果初始化了,直接返回
hash = mark->hash(); // this is a normal header
if (hash) { // if it has hash, just return it
return hash;
}
//分配一个hashcode
hash = get_next_hash(Self, obj); // allocate a new hash code
//将这个hashcode复制到对象头
temp = mark->copy_set_hash(hash); // merge the hash code into header
// use (machine word version) atomic operation to install the hash
test = (markOop) Atomic::cmpxchg_ptr(temp, obj->mark_addr(), mark);
if (test == mark) {
return hash;
}//这里的else,也就是cas失败之后,锁会再一次膨胀变成重量级锁
} else if (mark->has_monitor()) {
//如果本身就是重量级锁,那么hashcode存储在monitor中的markoop里面,ObjectMonitor的class在下文展示
monitor = mark->monitor();
temp = monitor->header();
assert (temp->is_neutral(), "invariant") ;
hash = temp->hash();
if (hash) {
return hash;
}
// Skip to the following code to reduce code size
} else if (Self->is_lock_owned((address)mark->locker())) {
//如果还在执行同步代码块
temp = mark->displaced_mark_helper(); // this is a lightweight monitor owned
assert (temp->is_neutral(), "invariant") ;
hash = temp->hash(); // by current thread, check if the displaced
if (hash) { // header contains hash code
return hash;
}
}
//膨胀成重量级锁
monitor = ObjectSynchronizer::inflate(Self, obj);
//获取重量级锁中的markoop
mark = monitor->header();
assert (mark->is_neutral(), "invariant") ;
hash = mark->hash();
//如果计算过hashcode,则直接返回
if (hash == 0) {
hash = get_next_hash(Self, obj);
temp = mark->copy_set_hash(hash); // merge hash code into header
assert (temp->is_neutral(), "invariant") ;
test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);
//如果cas失败了,则获取已经cas成功的hashcode
if (test != mark) {
hash = test->hash();
assert (test->is_neutral(), "invariant") ;
assert (hash != 0, "Trivial unexpected object/monitor header usage.");
}
}
return hash;
}
所以,如果对象计算了hashcod那么该对象的至少是轻量级锁,如果在计算hashcode中出现了竞争,或者hashcode在同步块中计算,那么会变成重量级锁。这么做是为了保证只有一个hashcode。
偏向锁总结
- 再jdk8中偏向锁的开启是由延迟的,默认4s,在4s内创建的对象为不可偏向。即使过了4s这部分对象也不能更改成偏向状态。
- 当一个线程尝试获取一个不偏向自己线程ID的对象时,偏向锁会升级成轻量级锁。
- 当一个线程死亡,它的ID可能会被复用,则之前偏向死亡的线程的mark word就自动偏向了复用ID的新线程。
- 当偏向锁被撤销时,类中的撤销计数器会+1,撤销阈值的上限为批量撤销的阈值。并记录撤销的时间。
- 当偏向锁撤销的次数达到批量重偏向阈值时,类中的epoch +1,触发批量重偏向。这时线程尝试获取一个不是偏向它的线程,该对象的mark word 不会膨胀为轻量级锁,而是直接偏向该线程。
- 当偏向锁撤销的次数达到达到批量撤销的阈值时,类中的偏向状态被取消,触发偏向锁的批量撤销。这时批量偏向的状态被取消。这时线程尝试获取一个不是偏向它的对象时,该对象的mark word 膨胀为轻量级锁。
- 当初发批量重偏向或批量撤销时,批量操作的是锁定状态的对象。对于不在锁定是通过改变类中的mark word,来隐式的使对象的mark word失效。对于新建的对象,则是直接获取类中mark word。
- 当偏向锁的撤销次数在批量重偏向和批量撤销的阈值之间时,如果长时间没有锁撤销偏向,类中的偏向撤销次数会被重置。
- 类中的偏向锁撤销次数并不能无限增加,上限是偏向锁批量撤销的阈值。所以如果批量撤销的阈值小于批量重偏向的阈值,就不会触发批量重偏向。
- 初始化过hashcode的对象不能处于偏向状态,如果在计算hashcode时存在竞争,则会升级为重量级锁。
- 偏向锁的重入会在栈中分配一个新的基础锁,而不是通过计数的方式记录重入的次数。