轻量级锁
使用jvm参数-XX:-UseBiasedLocking来关闭偏向锁。
例1: 这里关闭了偏向锁,来直接打打印在同步代码块执行中,和执行后对象头mark word的变化。
public class Test {
static class A{ }
public static void main(String[] args) throws Exception {
A a = new A();
synchronized (a){
System.out.println((ClassLayout.parseInstance(a).toPrintable()));
}
System.out.println((ClassLayout.parseInstance(a).toPrintable()));
}
}
//输出省略了klass pointer 和填充字节,下同
Test$A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) d8 f0 71 02 (11011000 11110000 01110001 00000010) (41021656)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
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)
回顾前文我们可以知道,当对象头为偏向锁时所标志位为00,指针指向堆栈上的实际头。 jvm的偏向锁开启有延迟,在4s内创建的对象,如果在同步代码块内,那么是轻量级锁而不是偏向锁,退出同步代码块后变成解锁状态(01)。
轻量级锁加锁源码分析
当偏向锁升级程轻量锁锁时,会调用 CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);方法
//这里传入当前的线程,和基础锁对象
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
//jvm可配置参数,是否需要打印锁统计信息
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
//jvm参数,是否使用偏向锁
if (UseBiasedLocking) {
//如果开启了偏向锁,那么这时候就要撤销偏向锁,膨胀为轻量级锁
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
//没有开启就直接膨胀为轻量级锁
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
"must be NULL or an object");
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END
我们先来看fast_enter,先撤销偏向锁,然后膨胀成轻量级锁的情况。
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
//jvm参数,是否使用偏向锁
if (UseBiasedLocking) {
//是否是在系统安全点,是的话直接撤销
if (!SafepointSynchronize::is_at_safepoint()) {
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
return;
}
} else {
assert(!attempt_rebias, "can not rebias toward VM thread");
//不是的话等待安全点在进行撤销
BiasedLocking::revoke_at_safepoint(obj);
}
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
//最后都要执行slow_enter
slow_enter (obj, lock, THREAD) ;
}
不在安全点直接撤销的情况revoke_and_rebias,根据传参得知attempt_rebias为true.
BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");
//获取该对象的mark word部分
markOop mark = obj->mark();
//这里判断对象头是否是匿名偏向的,就是没有偏向任何线程。如果是偏向锁撤销膨胀为轻量级锁,那么不可能是匿名的。
//这里是对于那些匿名偏向的对象,在计算了hashcode之后,会调用该方法进行偏向的撤销。且这时传来的attempt_rebias为false
if (mark->is_biased_anonymously() && !attempt_rebias) {
//计算hashcode造成的偏向锁撤销,会尝试不在安全点进行撤销这个操作.
markOop biased_value = mark;
markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
if (res_mark == biased_value) {
return BIAS_REVOKED;
}
} else if (mark->has_bias_pattern()) {
//判断对象是否有偏向模式
Klass* k = obj->klass();
markOop prototype_header = k->prototype_header();
if (!prototype_header->has_bias_pattern()) {
//这里判断该对象的类是否已经被取消了偏向状态,也就是是否触发了批量撤销
markOop biased_value = mark;
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);
assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked");
return BIAS_REVOKED;
} else if (prototype_header->bias_epoch() != mark->bias_epoch()) {
//判断类中的epoch和对象中的一致,如果不一致就是触发了批量重偏向.这时根据参数attempt_rebias进行重偏向还是直接撤销
if (attempt_rebias) {
//重偏向
assert(THREAD->is_Java_thread(), "");
markOop biased_value = mark;
markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);
if (res_mark == biased_value) {
return BIAS_REVOKED_AND_REBIASED;
}
} else {
//撤销
markOop biased_value = mark;
markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
if (res_mark == biased_value) {
return BIAS_REVOKED;
}
}
}
}
//这个方法再偏向锁中分析过,用来统计偏向撤销的次数,根据次数来返回对应的对应的值。
//如果对象没有偏向模式,或者偏向被取消了,则返回NOT_BIASED。
//如果刚好达到批量重偏向的阈值,返回 HR_BULK_REBIAS
//如果是刚好达到批量撤销的阈值,返回HR_BULK_REVOKE.
//如果仅仅是撤销当前对象的偏向,返回HR_SINGLE_REVOKE
HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);
if (heuristics == HR_NOT_BIASED) {
return NOT_BIASED;
} else if (heuristics == HR_SINGLE_REVOKE) {
//拿到该对象的类
Klass *k = obj->klass();
//拿到类的对象头
markOop prototype_header = k->prototype_header();
if (mark->biased_locker() == THREAD &&
prototype_header->bias_epoch() == mark->bias_epoch()) {
//这个if是已经偏向当前线程的对象,执行了object.hash的情况,会进入这个分支.
//所以匿名偏向的对象还是已经偏向某个线程的对象子计算了hashcode之后都会撤销偏向。
//这种情况也不需要等待线程安全点,可以直接撤销。
ResourceMark rm;
if (TraceBiasedLocking) {
tty->print_cr("Revoking bias by walking my own stack:");
}
EventBiasedLockSelfRevocation event;
//撤销偏向
BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD, NULL);
((JavaThread*) THREAD)->set_cached_monitor_info(NULL);
assert(cond == BIAS_REVOKED, "why not?");
if (event.should_commit()) {
event.set_lockClass(k);
event.commit();
}
return cond;
} else {
//这里就是当前线程不是对象已经偏向的线程,或者epoch过期了。无论是哪种,都需要再线程的安全点执行
EventBiasedLockRevocation event;
VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);
VMThread::execute(&revoke);
if (event.should_commit() && (revoke.status_code() != NOT_BIASED)) {
event.set_lockClass(k);
// Subtract 1 to match the id of events committed inside the safepoint
event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1);
event.set_previousOwner(revoke.biased_locker());
event.commit();
}
return revoke.status_code();
}
}
//剩下批量撤销和批量重偏向
assert((heuristics == HR_BULK_REVOKE) ||
(heuristics == HR_BULK_REBIAS), "?");
EventBiasedLockClassRevocation event;
VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,
(heuristics == HR_BULK_REBIAS),
attempt_rebias);
VMThread::execute(&bulk_revoke);
if (event.should_commit()) {
event.set_revokedClass(obj->klass());
event.set_disableBiasing((heuristics != HR_BULK_REBIAS));
// Subtract 1 to match the id of events committed inside the safepoint
event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1);
event.commit();
}
return bulk_revoke.status_code();
}
我们再来看slow_enter,这里是直接膨胀为轻量级锁。
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
//是否为无锁状态
if (mark->is_neutral()) {
//设置轻量级锁的锁头
lock->set_displaced_header(mark);
//成功就返回,如果失败就升级重量级锁
//将BasicLock的地址写入该对象的对象头中,也就是再例1中打印的地址。再解锁的时候同样要在进行一次cas还原
//其中lock是将要进行替换的值,obj()->mark_addr()是替换的目标地址,mark是预期值
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT (slow_enter: release stacklock) ;
return ;
}
// Fall through to inflate() ...
} else
//有锁状态判断是否重入,如果是的话,将锁头设置为NULL。不是重入说明存在竞争,升级重量级锁
if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
assert(lock != mark->locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
lock->set_displaced_header(NULL);
return;
}
#if 0
// The following optimization isn't particularly useful.
if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) {
lock->set_displaced_header (NULL) ;
return ;
}
#endif
//膨胀为重量级锁
lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
}
轻量级锁的解锁
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,只有再第一次加锁的时候会设置displaced_header的值
markOop header = lock->displaced_header();
//将BasicObjectLock中的_obj设置为空,这就相当于释放了锁
most_recent->set_obj(NULL);
//如果该对象的mark word 不是偏向模式,也就是说是轻量级锁或者重量级锁
if (!lockee->mark()->has_bias_pattern()) {
bool call_vm = UseHeavyMonitors;
//如果header为空,就说明是重入的锁
if (header != NULL || call_vm) {
//这里通过cas将第一次加锁时记录到BasicLock中的displaced_header取回来
//其中header是将要设置的值,lock是预期值
if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) != lock) {
//如果是重量级锁或者cas失败了,那么要将_obj重新塞回去
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();
}
如果不是偏向锁的话会调用CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception);
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
if (elem == NULL || h_obj()->is_unlocked()) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
// Free entry. This must be done here, since a pending exception might be installed on
// exit. If it is not cleared, the exception handling code will try to unlock the monitor again.
elem->set_obj(NULL);
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END
void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
fast_exit (object, lock, THREAD) ;
}
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");
// if displaced header is null, the previous enter is recursive enter, no-op
markOop dhw = lock->displaced_header();
markOop mark ;
if (dhw == NULL) {
//如果为空,说明是重入的情况,这里面没有做实际的操作就返回了
mark = object->mark() ;
assert (!mark->is_neutral(), "invariant") ;
if (mark->has_locker() && mark != markOopDesc::INFLATING()) {
assert(THREAD->is_lock_owned((address)mark->locker()), "invariant") ;
}
if (mark->has_monitor()) {
ObjectMonitor * m = mark->monitor() ;
assert(((oop)(m->object()))->mark() == mark, "invariant") ;
assert(m->is_entered(THREAD), "invariant") ;
}
return ;
}
mark = object->mark() ;
//用来判断lock中记录的的线程是否是当前的线程,如果不是则膨胀为重量级锁
if (mark == (markOop) lock) {
assert (dhw->is_neutral(), "invariant") ;
//cas失败同样膨胀为重量级锁
if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
TEVENT (fast_exit: release stacklock) ;
return;
}
}
ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}
轻量级锁总结
- 轻量级锁和偏向锁设置锁的方式是相同的,第一次加锁会在BasicLock初始化displaced_header。如果是重入的话只会设置BasicObjectLock中的_obj。栈中BasicObjectLock指向该对象的数量代表重入的次数。
- 如果该对象需要从偏向锁膨胀到轻量级锁,如么需要先取消偏向。
- 偏向锁在加锁和解锁的过程中都要使用cas进行markword的替换,如果失败了会膨胀成重量级锁,再进行相应的加锁或者解锁操作。
- 轻量级锁再进入同步代码块后,mark word记录的是BasicLock的地址,退出代码块后会还原成无锁。