Comments (78)
@yinjk 先回答第二个问题。
但是此处只是将锁对象的mark word设置为匿名偏向状态,而未通过CAS将偏向线程设置为当前B线程的ID,这个逻辑是在哪里实现的呢
非常抱歉,我上面的回答中关于重偏向的代码引用有误。
下面为正确答案:
对于B线程来说,逻辑在这里
对于其他case来说,是在下一次获得锁时发生([这里])(http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/runtime/biasedLocking.cpp#l576)。
这里其他case是指:发生批量重偏向时,该class的所有已经创建好的且不在同步块中锁对象。因为在批量重偏向时,class的epoch+1了,但是这些对象的epoch还是原值。
from myblog.
else {
// 走到这里说明当前要么偏向别的线程,要么是匿名偏向(即没有偏向任何线程)
// code 8:下面构建一个匿名偏向的mark word,尝试用CAS指令替换掉锁对象的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);)
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
// CAS修改成功
if (PrintBiasedLockingStatistics)
(* BiasedLocking::anonymously_biased_lock_entry_count_addr())++;
}
else {
// 如果修改失败说明存在多线程竞争,所以进入monitorenter方法
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
success = true;
}
这里有一个细节,避免有的同学跟我产生一样的误解,所以在评论里提一下,注意看 CAS 操作的参数,旧值是匿名偏向的 mark word ,也就是说这个 CAS 操作的本意是尝试把一个匿名偏向改为偏向当前本线程,如果此时该锁对象已经偏向某个线程,那么这个 CAS 操作肯定是失败的,就会进入到锁撤销的逻辑(即 InterpreterRuntime::monitorenter
方法里)。
我之前产生的误解是:“不管当前锁对象是否已经偏向某个线程,都会直接 CAS 尝试修改偏向为本线程”。这样做是不合理的,因为前一个线程很有可能还在执行同步代码块,这里如果 CAS 修改成功就表明当前线程获取偏向锁成功,就违反了锁的互斥性。
from myblog.
epoch在markWord中占用的是2bit位,也就是最大值为11 = 3,它是如何被增加到40以后修改成去掉偏向锁的呢?这个是bit不是byte呀!
from myblog.
@yinjk 关于第一个问题,应该是说:有且只有调用revoke_and_rebias
的线程被重偏向为另一个线程了,返回的才是BiasedLocking::BIAS_REVOKED_AND_REBIASE
(大致扫了下代码,应该是这样)。然后决定bulk_revoke.status_code()
值的代码在这里。VMThread::execute
得主要作用是将对应方法(bulk_revoke所在类的doit)放到VM Thread中执行,关于VM Thread的介绍可以看这里。
from myblog.
请问一下作者,文中偏向锁的撤销小节中,当线程A已经退出同步代码块了,此时线程B请求对象锁触发偏向锁撤销流程,锁对象根据是否允许重偏向被撤销为匿名偏向状态或无锁状态,若允许重偏向,线程B能直接获取到偏向锁吗,是否允许重偏向是如何判断的呢,期待解惑,万分感谢
from myblog.
@yinjk 当B请求锁时,如果没有触发到批量重偏向的阈值(update_heuristics
方法),那会被直接撤销为无锁状态。而如果到到达了重偏向的阈值,则会触发批量重偏向,逻辑在bulk_revoke_or_rebias_at_safepoint
方法中,首先会自增类中的epoch(代码),然后会调用revoke_bias
(代码),且第二个参数allow_rebias
为true。如果A已经退出同步块了但是还存活,则在revoke_bias
中的这里,会进行重偏向。
有不明白的地方可以再留言
from myblog.
感谢解答,文章写得很好很清楚,基本上理解了,但是还有一点点疑问,在ObjectSynchronizer::fast_enter
方法中(代码),只有方法revoke_and_rebias
返回值为BiasedLocking::BIAS_REVOKED_AND_REBIASE
才会return,表示获取到偏向锁,否则最终都会进入到slow_enter方法(代码),该方法是轻量级锁加锁方法,所以我猜测,当线程B触发的偏向锁撤销操作如果达到了批量重偏向的阈值,在这里返回的一定是BiasedLocking::BIAS_REVOKED_AND_REBIASE
,但是我现在无法验证我的猜想,因为我不知道bulk_revoke.status_code()
的值,我看VMThread::execute方法也没看明白(我不太会c++),希望作者大大指点一二;还有一个问题就是,你上面的回答说
如果A已经退出同步块了但是还存活,则在revoke_bias中的这里,会进行重偏向。
但是此处只是将锁对象的mark word设置为匿名偏向状态,而未通过CAS将偏向线程设置为当前B线程的ID,这个逻辑是在哪里实现的呢
from myblog.
非常感谢作者耐心解答,根据指示,问题已经得到解决。
from myblog.
感谢,最近一直在学习偏向锁方面的知识,自己也看了源码还是无法理解一些细节,拜读了此文之后疑问全解,感觉浑身通透
from myblog.
@zyllt 有帮助就好~
from myblog.
@yinjk 先回答第二个问题。
但是此处只是将锁对象的mark word设置为匿名偏向状态,而未通过CAS将偏向线程设置为当前B线程的ID,这个逻辑是在哪里实现的呢
非常抱歉,我上面的回答中关于重偏向的代码引用有误。
下面为正确答案:
对于B线程来说,逻辑在这里
对于其他case来说,是在下一次获得锁时发生([这里])(http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/9ce27f0a4683/src/share/vm/runtime/biasedLocking.cpp#l576)。%E3%80%82)
这里其他case是指:发生批量重偏向时,该class的所有已经创建好的且不在同步块中锁对象。因为在批量重偏向时,class的epoch+1了,但是这些对象的epoch还是原值。
有个疑问,如果没有达到批量重偏向应该执行的是单个重偏向,这里直接返回了BiasedLocking::BIAS_REVOKED,没有让B线程去竞争偏向锁。猜测可能是 VMThread::execute(&revoke);
这里的问题,对VMThread不是很熟悉,可以说明一下么,感谢~~
// 下面代码最终会在VM线程中的safepoint调用revoke_bias方法
VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);
VMThread::execute(&revoke);
return revoke.status_code();
from myblog.
@zyllt
首先从概念上来说只有批量重偏向,没有单个重偏向。批量重偏向需要用到VMThread,因为要操作其他线程的栈,所以需要在借助VMThread在safepopint中执行。
对于非批量重偏向的步骤如下:
1.如果线程A还存活且还在同步块中,则将锁对象升级为轻量级锁,同时需要修改线程A的栈,代码在这里。线程A不存活或不在同步块中,则先将锁对象设置为无锁状态,代码在这里。
2.在之后的slow_enter中,如果锁对象是无锁状态,则会升级为轻量级锁。否则升级为重量级锁。
也就是说,如果一个锁已经偏向线程A,当线程B尝试获得该锁时,无论线程A是什么状态,该锁都会升级成轻量级锁或重量级锁(不考虑批量重偏向的情况)。
如果你还不太理解,建议先看下上篇文章补充下概念上的知识。
from myblog.
@farmerjohngit 感谢回答,确实是我理解错了。
from myblog.
您的文章写的非常详细,受教了!我有一个问题,始终没有明白,在偏向锁入口code10,为什么在这个逻辑里处理偏向锁重入
// 如果偏向线程不是当前线程或没有开启偏向模式等原因都会导致success==false
if (!success) {
不是只有对同一个线程来讲才有锁重入的概念吗?既然如此,为什么偏向线程不是当前线程呢?还是我对锁重入的概念理解错了?麻烦您能指导一下,这个问题困惑我好久了,谢谢!
from myblog.
@ValiantYuan 走到这里说明已经是轻量级锁的逻辑了
from myblog.
@ValiantYuan 走到这里说明已经是轻量级锁的逻辑了
谢谢,明白了。
from myblog.
您好,我想请教一下,假如偏向锁持所线程Thread A已经不存活了,Thread B触发VMThread将偏向锁重置为匿名偏向后,Thread B在又何时重新CAS获取到偏向锁?
from myblog.
也就是说,如果一个锁已经偏向线程A,当线程B尝试获得该锁时,无论线程A是什么状态,该锁都会升级成轻量级锁或重量级锁(不考虑批量重偏向的情况)。
所以B普通情况下不会再获得该偏向锁。
而升级轻量级锁或重量级锁的逻辑在这里
from myblog.
是指只要Thread B在HR_SINGLE_REVOKE case下,因为revoke_bias()方法传的allow_rebias都是false,所以不会重偏向
也就是说,如果一个锁已经偏向线程A,当线程B尝试获得该锁时,无论线程A是什么状态,该锁都会升级成轻量级锁或重量级锁(不考虑批量重偏向的情况)。
所以B普通情况下不会再获得该偏向锁。
而升级轻量级锁或重量级锁的逻辑在这里
是指只要Thread B在HR_SINGLE_REVOKE case下,因为revoke_bias()方法传的allow_rebias都是false,所以不会重偏向?
还有几个问题也想一并请教一下
- epoch这个值,每次都是自增都是发生在safepoint里执行的,假如锁对象epoch过期,也只是将类对象中的epoch值更新到锁对象中。那锁对象中的epoch值有何作用?并没有在其他处用到,假如是为了记录锁批量重偏向次数,只保留类对象中的epoch不就可以了么?
- prototype_header是存在类对象当中的,类对象本身作为对象应该也有属于自己的MardWord,是么?若是的话,这两者之间有什么区别么?
- 关于批量重偏向,依我的理解,假如Thread B获取所,第一次发生锁撤销,revocation_count从0到1,就进入HR_SINGLE_REVOKE case进行锁升级了。如果不考虑重置revocation_count,同一个类的锁对象只有第20次到40次之间的锁撤销才有可能重偏向
- 下述中提到epoch值过期也要进入到锁撤销,而不是CAS重偏向失败才进入的么?
以上是偏向锁加锁的流程(包括部分轻量级锁的加锁流程),如果当前锁已偏向其他线程||epoch值过期||偏向模式关闭||获取偏向锁的过程中存在并发冲突,都会进入到InterpreterRuntime::monitorenter方法, 在该方法中会对偏向锁撤销和升级。
中提到epoch值过期也要进入到锁撤销,而不是CAS重偏向失败才进入的么?
from myblog.
@DanFL 最近比较忙,你的问题我慢慢回答哈
是指只要Thread B在HR_SINGLE_REVOKE case下,因为revoke_bias()方法传的allow_rebias都是false,所以不会重偏向?
是
from myblog.
epoch这个值,每次都是自增都是发生在safepoint里执行的,假如锁对象epoch过期,也只是将类对象中的epoch值更新到锁对象中。那锁对象中的epoch值有何作用?并没有在其他处用到,假如是为了记录锁批量重偏向次数,只保留类对象中的epoch不就可以了么?
epoch的作用是在触发批量重偏向阈值后,下次获得锁时,通过判断class的epoch和obj的epoch决定要不要直接重偏向。 这点在本文中也说过了,建议你将本文开头处的monitorenter
和revoke_and_rebias
相关代码在看一遍,我在注释中都有说明
from myblog.
下述中提到epoch值过期也要进入到锁撤销,而不是CAS重偏向失败才进入的么?
这里我写的有问题,是重偏向失败才进入
from myblog.
prototype_header是存在类对象当中的,类对象本身作为对象应该也有属于自己的MardWord,是么?若是的话,这两者之间有什么区别么?
如果你说的是Java层的类对象,那是的,因为类对象本身也是对象。至于你问的区别,markword是对象的(包括类对象),Native层对应的是oop->mark()
;而prototype_header是类的,Native层的klass->prototype_header()
如果你说的是Native层的Klass,那不是的,klass.hpp中并没有markword
只有prototype_header
from myblog.
水平不够 看源码实在是受不住啊 全靠注释看下来了 有个小问题 在锁升级的时候:
将偏向线程所有相关Lock Record的Displaced Mark Word设置为null,然后将最高位的Lock Record的Displaced Mark Word 设置为无锁状态,最高位的Lock Record也就是第一次获得锁时的Lock Record(这里的第一次是指重入获取锁时的第一次)
偏向锁重入也会插入一次Lock Record嘛?那为什么上面偏向锁重入那里的注释是什么也没做呢?
然后是括号里的内容 为什么是一次重入获取的锁 第一次偏向的时候不就分配了一个Lock Record嘛 为什么用的是重入加的Lock Record呢?
from myblog.
偏向锁重入也会插入一次Lock Record嘛?那为什么上面偏向锁重入那里的注释是什么也没做呢?
code1 和 code2处已经拿到一个空闲的Lock Record,然后指向当前锁对象了。
from myblog.
然后是括号里的内容 为什么是一次重入获取的锁 第一次偏向的时候不就分配了一个Lock Record嘛 为什么用的是重入加的Lock Record呢?
不太记得了 不排除我搞错了
from myblog.
如果要撤销的锁偏向的不是当前线程,会将该操作push到VM Thread中等到safepoint的时候再执行。
等待safepoint的这个过程中,当前线程是继续往下走到slow_enter方法里面吗?还是阻塞直到升级成为轻量级锁?
from myblog.
@May0302 在VM_RevokeBias的模式下,VMThread::execute是阻塞的
from myblog.
如果一个系统都用object类作为锁,是不是很可能导致这个object锁被撤销?
from myblog.
看了您的文章受益匪浅,但是也有以下几个问题,期待您的解答,谢谢!
- 每次进入monitor_enter就创建一个lock record是哪段代码,没找到啊
2.偏向锁释放后lock record怎么办,就留在栈帧中吗? - 偏向锁升级时发现当前持有当前对象的线程已经不再使用,那么会将其置为无锁状态,然后升级,此时这个无锁状态中有哪些字段?还有hashcode吗?
- 轻量级锁解锁后,用cas替换对象的markword为栈帧中的displaced mark word,此时displaced mark word应该是锁升级前的,那么此时轻量级锁是不是就完全释放,下一次加锁前是偏向/无锁状态,而不是直接进入轻量级锁?这样不就是锁降级了?
from myblog.
还有一个问题,对象mark word中的锁标志位是在哪块修改的,只看到CAS修改指针地址,没看到对锁标志位修改,谢谢!
from myblog.
比如程序员如何知道锁的状态呢?有没有办法直观看到某个时候锁的状态,比是不是偏向锁,是不是重量级锁?
from myblog.
看了受益匪浅!!有个问题想问问
在批量重偏向的时候会对klass的epoch自增,然后在对klass所有锁对象的epoch自增。在这个过程中是否会有影响到其他锁对象的线程安全性?例如刚对klass的epoch自增了,然后立马有个线程过来尝试判断 else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) ,接下来的cas代码问看了看应该是可以直接成功的,这样如果线程a还在同步块中的话,线程b通过这里也获得了偏向锁(强调是klass的epoch刚自增)。还有就是为啥对klass的epoch自增,然后要对klass所有锁对象的epoch自增?意义在哪,感觉跟epoch过时可以重偏向有点冲突,读源码能力有限,望解答解答
from myblog.
将撤销偏向锁的操作放到进入SafePoint时执行,VMThread::execute(&revoke)是会阻塞当前线程的执行吗?
VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);
VMThread::execute(&revoke);
return revoke.status_code();
from myblog.
会走到该方法的逻辑有很多,我们只分析最常见的情况:假设锁已经偏向线程A,这时B线程尝试获得锁。
上面的code 1,code 2B线程都不会走到,最终会走到code 4处,如果要撤销的锁偏向的是当前线程则直接调用revoke_bias撤销偏向锁,否则会将该操作push到VM Thread中等到safepoint的时候再执行。
这里的当前线程
指的是线程A? @farmerjohngit
from myblog.
偏向锁是在 同步代码块之后释放吗?
from myblog.
你好,对象头中,轻量级锁的标识是 00 ,为什升级为轻量级锁的时候,却把Displaced header设置为锁状态的 01,这样轻量级锁释放的时候,对象头不是变成01(无锁)了吗。不应该是00(轻量级锁)吗
from myblog.
大神你好,看了很多遍,受益匪浅,奈何实在不懂C++代码。我想请教下,Lock Record是在执行_monitorenter前就已经在栈中创建好了吗?C++代码中没看到有这块的内容。当然你的博客中有提及
from myblog.
9,这一步已经是轻量级锁的逻辑了。从上图的mark word的格式可以看到,轻量级锁中mark word存的是指向Lock Record的指针。这里构造一个无锁状态的mark word,然后存储到Lock Record(Lock Record的格式可以看第一篇文章)。设置mark word是无锁状态的原因是:轻量级锁解锁时是将对象头的mark word设置为Lock Record中的Displaced Mark Word,所以创建时设置为无锁状态,解锁时直接用CAS替换就好了。
并不是构造一个无锁状态的mark word,只是把最后一位置为1而已,避免是轻量级或者重量级锁
from myblog.
在调用 hashcode 方法时轻量级锁也需要锁升级吗?我理解轻量级锁里 Lock Word 的 displaced mark word 不是可以存 hashcode 嘛
from myblog.
Lock Record是在执行_monitorenter前就已经在栈中创建好了吗?C++代码中没看到有这块的内容。当然你的博客中有提及
是在私有线程栈找到一个最近并且空闲的区域,创建一个Lock Record,里面属性为null。
BasicObjectLock* limit = istate->monitor_base();
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
BasicObjectLock* entry = NULL;
from myblog.
你好,对象头中,轻量级锁的标识是 00 ,为什升级为轻量级锁的时候,却把Displaced header设置为锁状态的 01,这样轻量级锁释放的时候,对象头不是变成01(无锁)了吗。不应该是00(轻量级锁)吗
轻量级锁释放的时候,即跳出临界区,要把对象设置为无锁的状态
from myblog.
没有办法直观看到某个时候锁的状态,比是不是偏向锁,是不是重量级锁
在pom.xml添加中:
org.openjdk.jol
jol-core
0.9
log.debug(ClassLayout.parseInstance(l).toPrintable());
这个可以打印对象头 查看到对象头信息
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 68 f0 5e 20 (01101000 11110000 01011110 00100000) (543092840)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 63 8c 01 f8 (01100011 10001100 00000001 11111000) (-134116253)
12 4 (loss due to the next object alignment)
from myblog.
<!--sun公司 专门解析对象实例,查看对象布局-->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
from myblog.
你好,作者,有一些问题,不是很明白。
假设偏向延迟关闭的情况下,t1线程 进入代码之后,释放了。t2线程来进入代码,升级成轻量锁,释放了锁。对象l 变成一个无锁的状态00000001,这时只有t3线程来加锁,success=false , 会进入if (!success) ,构建在内存的无锁markword 01,比较当前持有对象l 的markword 与内存中产生是否相同,相同则l 对象的对象头指向当前线程的锁记录。
这步没有问题,但是有疑问是CAS加锁成功后,l对象的markword 原来01 ,但我们知道轻量锁加锁的时候,会把对象头改为00,但我看了好久,都没有找到相对应的代码?CAS已经成功了,好像也不会进入膨胀过程里CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
不知道JVM是怎么把对象头的01无锁改为00轻量锁的?
from myblog.
想请问一下:
(1)当线程第一次获得偏向锁的时候,是否会在栈中添加一个空的Lock Record,若不会,则当其他线程来竞争时才会往持有锁的线程添加Lock Record吗?否则,怎么进行升级轻量级锁。
【我的理解:要添加一个空的Lock Record,其中只设置obj指向锁的对象】
(2)当偏向锁重入的时候,是否要继续添加Lock Record?
【我的理解,同样需要,Lock Record和第一次添加的相同,若不添加Lock Record怎么确定锁重入的次数】
from myblog.
是的。要添加一个空的Lock Record,其中只设置obj指向锁的对象。但是在不同的栈帧里面。synchronized{ //1 synchronized{ //2 } //3 } //4 只要进入 synchronized 对应 _monitorenter,只要退出synchronized 对应 _monitorexit;所以两个Lock Record 并没有冲突的。
噢,感谢作者!我大概明白了,总的来说就是:
-
如果偏向锁第一次进入,经过所有检测后,会在当前线程栈帧中找到内存地址最高的可用Lock Record,Displaced Mark Word会将线程ID存入,同时obj指向对象,并修改对象头为线程ID
-
如果是偏向锁重入,则只会添加一个DMW为空的Lock Record,其中obj还是会指向对象
-
当偏向锁释放时,只会从底到高,将obj相符的Lock Record的obj设置为null,不会改变对象Mark Word的threadId
from myblog.
最后一个小疑问就是,我在代码中似乎没看到偏向锁重入时会添加一个空的lock Record(虽然大概率是我理解错了)
// code 5:如果偏向的线程是自己且epoch等于class的epoch if (anticipated_bias_locking_value == 0) { // **already biased towards this thread, nothing to do** if (PrintBiasedLockingStatistics) { (* BiasedLocking::biased_lock_entry_count_addr())++; } success = true; }
我的理解是偏向锁重入时,也是走code5,可是在这里面并没有做任何事情,同时success为true。
而我认为添加空Lock Record的代码只有在处理轻量级锁的流程里面,也就是if (! successs){...}
中的
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) { //code 10: 如果是锁重入,则直接将Displaced Mark Word设置为null entry->lock()->set_displaced_header(NULL); } else { CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception); }
希望作者给予最后的指点!谢谢!
from myblog.
from myblog.
在阅读偏向锁的源码后,自己对知识做了测试验证,其中下面有一个理解上面的确认 和 还有一个没有理解清楚的疑问,下面先说明一下测试的情况:
synchronized (lockObj) {
System.out.println(ClassLayout.parseInstance(lockObj).toPrintable());
// biased_lock偏向锁标识位+lock锁状态标识位=101
}
// code 1
new Thread(() -> {
synchronized (lockObj) {
System.out.println(ClassLayout.parseInstance(lockObj).toPrintable()); // code 2
//biased_lock偏向锁标识位+lock锁状态标识位=000,轻量级锁状态。
}
}).start();
I,在code1 没有代码的代码时候,上面code2打印是轻量级锁状态000。
这个状态我的理解1:这个按照源码的推演,在执行第二个synchronized时:应该是批量重偏向的阈值还未达到,所以先阻塞进入安全点->再修改成无锁状态,->最终膨胀成轻量级锁(ObjectSynchronizer::slow_enter中在)。
II,如果在code1出加入 System.gc()后,则在code 2 打印的锁状态为偏向锁101。
这个状态我的理解2:这里为什么手动触发一次fullgc就会重偏向呢? 我猜测fullgc会进入全局安全点会将class的epoch+1,但是锁对象的不变(没有找到代码验证此猜测,也是我疑惑的点),导致2者不一致。最终在执行第二个synchronized时,由于class和锁实例的epoch不一致,会触发重偏向新线程。源码
第二个测试验证:
new Thread(() -> {
synchronized (lockObj) {
System.out.println(ClassLayout.parseInstance(lockObj).toPrintable());
// biased_lock偏向锁标识位+lock锁状态标识位=101
}
}).start();
new Thread(() -> {
synchronized (lockObj) {
System.out.println(ClassLayout.parseInstance(lockObj).toPrintable()); //code3
// biased_lock偏向锁标识位+lock锁状态标识位=101
}
}).start();
上面代码改成2个独立的线程。 这里却为什么 在code3 这里却是打印为偏向锁状态(等于发生了单次重偏向)。
我的理解3: 未知!!! 这里按理跟 ‘理解1’那里的情况是一样,应该也是 轻量级锁状态,因为lockObj都不在同步块,并且前面已经有过偏向,而且有没有出现epoch不一致的情况,这里为何就是 做了一次重偏向呢?
我用的jdk1.9测试的。 请教一下几个疑问,
1,上面我的理解 1 是否正确?
2,理解2中的猜测是否正确,验证的代码在哪?
3,上面我的理解3是未知的,希望能帮忙解惑以下
@farmerjohngit
@WLWL @wangliyu0328
from myblog.
@xdcode2020
我使用jdk1.8测试,
我能不能看一下,你的第一个测试例子完整代码呢?
因为我在测试第一个例子时,不论是否 System.gc(),// code 2始终是轻量锁,00,只是在没有System.gc()的时候,分代年龄会是0,System.gc后,分代年龄变成1。
这是我的完整代码:
@Slf4j(topic = "enjoy")
public class TestJol {
static LockObj lockObj=new LockObj();
static Thread t1;
public static void main(String[] args) throws InterruptedException {
log.debug("front :"+ClassLayout.parseInstance(lockObj).toPrintable());
synchronized (lockObj){
log.debug("main :"+ClassLayout.parseInstance(lockObj).toPrintable());
}
System.gc();
t1=new Thread(()->{
synchronized (lockObj){
log.debug("t1 :"+ClassLayout.parseInstance(lockObj).toPrintable());
}
});
t1.start();
t1.join();
log.debug("main end :"+ClassLayout.parseInstance(lockObj).toPrintable());
}
}
至于第二个例子,可能会出现,这样的情况,CPU使用第一个线程 t1 后,结束后,代码在启动的线程可能还是t1。并不是重新启动另一个线程。但也可能不会出现这样的情况。根据时间片轮转,可能也不会出现这个问题。看机率。
from myblog.
使用博主你的代码,我测试: 在JDK9时:加了GC后,第二个同步块的锁为:101偏向锁(跟博主结果不一样), 没有加GC后,就是轻量级锁(跟博主结果一样),有点疑惑了 . 在用jdk8(jdk1.8.0_181,这个为非JDK8U版本吧)不管加不加GC,都是轻量级锁。
ps:我为什么要用JDK9,是因为看源码就如博主所说jdk8u和jdk8 偏向锁的源码存在差别,在核对与jdk8u以上的版本是一致的(特意对比了一下 jdk9 和jdk8u的几个偏向锁的核心函数的源码,逻辑都是一样的,只是打印日志做了些调整),所以采用JDK9也进行了一次对比测试。
想向博主和大家先确定一下这个认识是否正确?
按照jdk8 偏向锁的源码,如果第一个线程A偏向后(重偏向阈值未到达,并且第一个线程A已跳出同步块),另外一个线程B再进入同步块,中我阅读的理解是不会重偏向,而是升级为同步锁,这个理解是否正确?
以上理解的源码调用栈分析依据:
第一步:revoke_and_rebias函数,因为class和对象的epoch是一致的(之前没有进入过安全点),所以会走这个分支阻塞等待进入安全点: 源码
第二步:安全点到达后,最终会调用revoke_bias函数,因为main线程存活,但已经跳出同步块,所以会走这个设置无锁状态:源码二
第三步:进入ObjectSynchronizer::slow_enter中升级成轻量级锁。
JVM配置:-XX:BiasedLockingStartupDelay=0 关闭偏向锁延迟效果
2个测试的jdk版本:
测试代码(使用的博主的):
static LockObj lockObj = new LockObj();
static Thread t1;
public static void main(String[] args) throws InterruptedException {
// Thread.sleep(10000);
System.out.println("1:" + ClassLayout.parseInstance(lockObj).toPrintable());
synchronized (lockObj) {
System.out.println("main :" + ClassLayout.parseInstance(lockObj).toPrintable());
}
System.gc(); //下面有加与不加 这行代码的测试结果.
t1 = new Thread(() -> {
synchronized (lockObj) {
System.out.println("t1 :" + ClassLayout.parseInstance(lockObj).toPrintable());
}
});
t1.start();
t1.join();
System.out.println("main end :" + ClassLayout.parseInstance(lockObj).toPrintable());
}
加了System.gc();
JDK9的输出:
"C:\Program Files\Java\jdk-9.0.4\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:60094,suspend=y,server=n -XX:BiasedLockingStartupDelay=0 -javaagent:C:\Users\xxx\AppData\Local\JetBrains\IdeaIC2020.2\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Users\86133\IdeaProjects\untitled1\target\classes;C:\Users\xxx\.m2\repository\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.2.3\lib\idea_rt.jar" ThreadTest
Connected to the target VM, address: '127.0.0.1:60094', transport: 'socket'
# WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf
1:LockObj 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)
8 4 (object header) 92 d7 00 20 (10010010 11010111 00000000 00100000) (536926098)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main :LockObj object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 90 a7 7e (00000101 10010000 10100111 01111110) (2124910597)
4 4 (object header) 15 02 00 00 (00010101 00000010 00000000 00000000) (533)
8 4 (object header) 92 d7 00 20 (10010010 11010111 00000000 00100000) (536926098)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t1 :LockObj object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 32 23 (00000101 00000000 00110010 00100011) (590479365)
4 4 (object header) 15 02 00 00 (00010101 00000010 00000000 00000000) (533)
8 4 (object header) 92 d7 00 20 (10010010 11010111 00000000 00100000) (536926098)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main end :LockObj object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 32 23 (00000101 00000000 00110010 00100011) (590479365)
4 4 (object header) 15 02 00 00 (00010101 00000010 00000000 00000000) (533)
8 4 (object header) 92 d7 00 20 (10010010 11010111 00000000 00100000) (536926098)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Disconnected from the target VM, address: '127.0.0.1:60094', transport: 'socket'
Process finished with exit code 0
JDK8的输出:
"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:60117,suspend=y,server=n -XX:BiasedLockingStartupDelay=0 -javaagent:C:\Users\xxx\AppData\Local\JetBrains\IdeaIC2020.2\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\Users\xxx\IdeaProjects\untitled1\target\classes;C:\Users\86133\.m2\repository\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.2.3\lib\idea_rt.jar" ThreadTest
Connected to the target VM, address: '127.0.0.1:60117', transport: 'socket'
1:LockObj 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)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main :LockObj object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 38 62 03 (00000101 00111000 01100010 00000011) (56768517)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t1 :LockObj object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 28 f4 ea 1b (00101000 11110100 11101010 00011011) (468382760)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main end :LockObj object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 09 00 00 00 (00001001 00000000 00000000 00000000) (9)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Disconnected from the target VM, address: '127.0.0.1:60117', transport: 'socket'
Process finished with exit code 0
不加System.gc();
JDK9的输出:
"C:\Program Files\Java\jdk-9.0.4\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:60161,suspend=y,server=n -XX:BiasedLockingStartupDelay=0 -javaagent:C:\Users\86133\AppData\Local\JetBrains\IdeaIC2020.2\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Users\86133\IdeaProjects\untitled1\target\classes;C:\Users\86133\.m2\repository\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.2.3\lib\idea_rt.jar" ThreadTest
Connected to the target VM, address: '127.0.0.1:60161', transport: 'socket'
# WARNING: Unable to get Instrumentation. Dynamic Attach failed. You may add this JAR as -javaagent manually, or supply -Djdk.attach.allowAttachSelf
1:LockObj 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)
8 4 (object header) 92 d7 00 20 (10010010 11010111 00000000 00100000) (536926098)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main :LockObj object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 60 b3 0d (00000101 01100000 10110011 00001101) (229859333)
4 4 (object header) c6 01 00 00 (11000110 00000001 00000000 00000000) (454)
8 4 (object header) 92 d7 00 20 (10010010 11010111 00000000 00100000) (536926098)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t1 :LockObj object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 90 f3 ef 7a (10010000 11110011 11101111 01111010) (2062545808)
4 4 (object header) 99 00 00 00 (10011001 00000000 00000000 00000000) (153)
8 4 (object header) 92 d7 00 20 (10010010 11010111 00000000 00100000) (536926098)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main end :LockObj 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)
8 4 (object header) 92 d7 00 20 (10010010 11010111 00000000 00100000) (536926098)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Disconnected from the target VM, address: '127.0.0.1:60161', transport: 'socket'
Process finished with exit code 0
JDK8的输出:
"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:60152,suspend=y,server=n -XX:BiasedLockingStartupDelay=0 -javaagent:C:\Users\xxx\AppData\Local\JetBrains\IdeaIC2020.2\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;C:\Users\xxx\IdeaProjects\untitled1\target\classes;C:\Users\86133\.m2\repository\org\openjdk\jol\jol-core\0.9\jol-core-0.9.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2020.2.3\lib\idea_rt.jar" ThreadTest
Connected to the target VM, address: '127.0.0.1:60152', transport: 'socket'
1:LockObj 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)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main :LockObj object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 38 c5 02 (00000101 00111000 11000101 00000010) (46479365)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t1 :LockObj object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) c8 f0 52 1b (11001000 11110000 01010010 00011011) (458420424)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main end :LockObj 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)
8 4 (object header) 92 c3 00 20 (10010010 11000011 00000000 00100000) (536920978)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Disconnected from the target VM, address: '127.0.0.1:60152', transport: 'socket'
Process finished with exit code 0
from myblog.
@wangliyu0328
至于你说的第二个问题的分析原因,我之前也考虑过。 特意打印过线程ID,是2个不同的线程ID。 而且不是机率问题,是每次都是发生了重偏向
from myblog.
你好,作者,有一些问题,不是很明白。
假设偏向延迟关闭的情况下,t1线程 进入代码之后,释放了。t2线程来进入代码,升级成轻量锁,释放了锁。对象l 变成一个无锁的状态00000001,这时只有t3线程来加锁,success=false , 会进入if (!success) ,构建在内存的无锁markword 01,比较当前持有对象l 的markword 与内存中产生是否相同,相同则l 对象的对象头指向当前线程的锁记录。
这步没有问题,但是有疑问是CAS加锁成功后,l对象的markword 原来01 ,但我们知道轻量锁加锁的时候,会把对象头改为00,但我看了好久,都没有找到相对应的代码?CAS已经成功了,好像也不会进入膨胀过程里CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
不知道JVM是怎么把对象头的01无锁改为00轻量锁的?
@wangliyu0328 同样的疑惑,怀疑是不是Cas修改了record后也会同时修改锁状态?我还提了一个类似的问题
from myblog.
@xdcode2020
我昨天下载jdk1.9 测试,正如你所说第一个例子测试手动GC,将会线程t1将会重偏向自己。jdk1.8并不会。但我发现 jdk1.9 System.gc();手动GC一次,会清除对象头数据,使lockObj 对象 00000101 00000000 00000000 00000000。
当线程t1来加锁使,就会发生t1偏向自己的情况。我现在还在找相关代码,并不知道System.gc() 里面到底做了什么。这应该是jdk9的优化。
”按照jdk8 偏向锁的源码,如果第一个线程A偏向后(重偏向阈值未到达,并且第一个线程A已跳出同步块),另外一个线程B再进入同步块,中我阅读的理解是不会重偏向,而是升级为同步锁“
是这样理解的。最终会进入源码
升级成轻量级锁。
你的第二个例子验证:
2个独立的线程。code3 这里却是打印为偏向锁状态(等于发生了单次重偏向)。我这里使用jdk1.9 2个独立线程,测试的时候,有没有发生重偏向情况。第一个线程是重偏向,第二个线程是轻量锁。我这里测试,手动GC,只会影响main主线程,会发生清除对象头数据。
from myblog.
@scn7th
第二个问题,偏向锁升级时发现当前持有当前对象的线程已经不再使用,那么会将其置为无锁状态,然后升级,此时这个无锁状态中有哪些字段?还有hashcode吗?
无锁:没有使用:25位,hash:31位 ,没有使用:1位 GC年龄:4位 偏向标识:1 锁状态:01
如果你一开始代码就没有对对象设置hashcode hashcode 全部为0;
第三个问题 轻量级锁解锁后,用cas替换对象的markword为栈帧中的displaced mark word,此时displaced mark word应该是锁升级前的,那么此时轻量级锁是不是就完全释放下一次,加锁前是偏向/无锁状态,而不是直接进入轻量级锁?这样不就是锁降级了?
不是锁降级,锁的降级是从重量锁--->轻量锁--->偏向锁。
t1线程来加锁,退出后,释放锁,对象l 为偏向t1。t2线程来加锁,需要把t1的锁,撤销了,变成一把无锁的001,之后对象升级为轻量锁00.displaced mark word实际存的的确是锁升级前的值,但锁升级前,已经是无锁的001。需要进入_monitorexit。
from myblog.
@baixinping0 升级为轻量锁前,就是把Displaced header设置为锁状态的 01,轻量级锁释放的时候,对象头变成01(无锁)了。轻量锁升级的时候,CAS把锁标识设置为00。但轻量锁释放的时候,为01的。
from myblog.
epoch在markWord中占用的是2bit位,也就是最大值为11 = 3,它是如何被增加到40以后修改成去掉偏向锁的呢?这个是bit不是byte呀!
是通过一个 int revocation_count变量,为该类偏向锁撤销的次数。代码 在这里
默认值为40,在globals.hpp里设置的。
from myblog.
@aLibeccio
在调用 hashcode 方法时轻量级锁也需要锁升级吗?我理解轻量级锁里 Lock Word 的 displaced mark word 不是可以存 hashcode 嘛
不是太明白的问题。
如果是偏向锁,对象头mark word 由54位线程id+2位 epoch+未标识1位+GC年龄4位+偏向标识1位+锁标识2位。如果调用 hashcode 方法,hashcode 就占了31位,线程id 就放不下了。只能从偏向锁升级为轻量锁,如果发现锁竞争,就要升级为重量锁。轻量级锁需要锁升级时候,应该是有锁竞争的情况。
from myblog.
@farmerjohngit 您好,我有一个疑问就是
anticipated_bias_locking_value =
(((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) &
~((uintptr_t) markOopDesc::age_mask_in_place);
这里从klass的prototype_header中获取了一些信息,来判断偏向锁是否指向当前线程,epoch是否过期等,但是等偏向锁指向当前线程
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
后,并没有看见更新klass的prototype_header更新,那偏向锁重入就会有问题,请问prototype_header是在哪里更新的?
from myblog.
@farmerjohngit 您好,我有一个疑问就是
anticipated_bias_locking_value = (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) & ~((uintptr_t) markOopDesc::age_mask_in_place);
这里从klass的prototype_header中获取了一些信息,来判断偏向锁是否指向当前线程,epoch是否过期等,但是等偏向锁指向当前线程
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
后,并没有看见更新klass的prototype_header更新,那偏向锁重入就会有问题,请问prototype_header是在哪里更新的?
- 偏向锁偏向某个线程时不会修改prototype_header
- 偏向锁的重入和LockRecord有关,不知道问题在哪
- klass的prototype_header只会在批量重偏向和批量撤销时修改
我也看了下这块的代码,可以参考下这个:https://github.com/HenryChenV/my-notes#synchronized%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90
from myblog.
@farmerjohngit 您好,我有一个疑问就是
anticipated_bias_locking_value = (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) & ~((uintptr_t) markOopDesc::age_mask_in_place);
这里从klass的prototype_header中获取了一些信息,来判断偏向锁是否指向当前线程,epoch是否过期等,但是等偏向锁指向当前线程
if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) {
后,并没有看见更新klass的prototype_header更新,那偏向锁重入就会有问题,请问prototype_header是在哪里更新的?
- 偏向锁偏向某个线程时不会修改prototype_header
- 偏向锁的重入和LockRecord有关,不知道问题在哪
- klass的prototype_header只会在批量重偏向和批量撤销时修改
我也看了下这块的代码,可以参考下这个:https://github.com/HenryChenV/my-notes#synchronized%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90
谢谢答疑。
对于您的第二点
偏向锁的重入和LockRecord有关,不知道问题在哪
我的疑问是,偏向锁偏向某个线程时不会修改prototype_header那么重入时下面的分支语句该怎么走?
if (anticipated_bias_locking_value == 0) {
//分支1: 不会进入到这里,因为偏向锁偏向某个线程时不会修改prototype_header并没有指向当前线程,
prototype_headeranticipated_bias_locking_value !=0
...
}
else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
// 分支2
...
}
else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) {
// 分支3
...
}
else {
// 分支4: 走到这里说明当前要么偏向别的线程,要么是匿名偏向(即没有偏向任何线程)
...
}
在没有修改prototype_header的时候,分支1肯定不会走到,而分支4按楼主描述是处理匿名偏向的,那会走到分支3还是分支2?
from myblog.
你好,有一个小的细节问题,想要请教一下。在锁撤销部分的偏向标志被关闭时的代码(就是这段else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) {
)进行了CAS
我不太理解为什么这里的CAS在构造header时需要去取prototype_header的相关信息,我感觉按理来说这里应该是尝试恢复无锁状态,那么应该和if(!success)
中类似的方法去构造替换lockeet->mark_addr()的header才对。
简单来说,问题就是:在偏向模式关闭,则尝试撤销偏向锁时,为什么要替换lockee markword的,是class中的mark word,而不是构造一个无锁的header?
还望赐教。
from myblog.
@DanchuoZhong 偏向锁批量撤销 :当一个线程撤销次数20次时,会统一把这个对象 A a的类, 只要含有A这个类的锁,都撤销为无锁状态。CAS在构造header时需要去取prototype_header的相关信息,是去取类的信息,而不仅仅是对象a 的锁信息。lockeet->mark_addr()是取是对象的mark地址。
from myblog.
请教大佬一个问题。
现在有一个场景:线程t1加锁然后释放(偏向锁),然后t2来加锁再释放(轻量锁),然后t3来加锁(轻量锁)。
当t2释放的时候,mark word应该是如下图紫色部分吧,接着t3来加锁
t3来加锁执行到源码中(如下图)的这个判断的时候,是会返回true吗?
我请教了别人,得到的回复如下图(红框部分),这使我很困惑。
如果t3(轻量锁)来加锁,这个判断是true的话,那它接着执行里面的多个判断,在当前场景下里面的if和else if不是都不成立吗?接着会执行else代码块
请大神解惑!感谢~
from myblog.
@XHxin
你好,对于mark->has_bias_pattern()是否为true,含义是 Java有没有把偏向延迟禁用;因为在dk6默认开启偏向锁,即程序刚启动创建的对象是不会开启偏向锁的,几秒后后创建的对象才会开启偏向锁的;
作者这里是假设禁用偏向延迟,如果是禁用偏向延迟,if(mark->has_bias_pattern()) 是为ture;
//关闭延迟开启偏向锁 -XX:BiasedLockingStartupDelay=0
有一个场景:线程t1加锁然后释放(偏向锁),然后t2来加锁再释放(轻量锁),然后t3来加锁(轻量锁);
success=false,走轻量锁逻辑。
from myblog.
开局就没看懂= =
//entry不为null,代表还有空闲的Lock Record
为啥需要空闲的LockRecord
from myblog.
偏向锁Lock Record怎么生成的呀
from myblog.
请问代码注释里success==false的逻辑是不是有点问题?我的理解是只有对象头不是偏向锁状态或者class标记为不可偏向,才会导致success==false;如果偏向其他线程,虽然会调用monitorenter,但是success被修改成true,不会重新走success==false的流程
from myblog.
请问代码注释里success==false的逻辑是不是有点问题?我的理解是只有对象头不是偏向锁状态或者class标记为不可偏向,才会导致success==false;如果偏向其他线程,虽然会调用monitorenter,但是success被修改成true,不会重新走success==false的流程
我也是这么想的,偏向锁获取那段代码code 9的success=false只有一种情况就是klass的prototype_header中是否偏向+锁标志不是101,这种情况就会走code 9普通轻量级锁的路线,偏向其他线程的话应该走的是code 8里CAS失败的else部分。。。
from myblog.
看了文章有两个地方一直困惑我,麻烦请教一下:
lock record最开始是什么时候被put 到线程栈的?
偏向锁从内存低位置向上寻找空闲lock record,什么情况下会找不到?找不到的话会怎样?
from myblog.
必须给个
from myblog.
你好,有个疑问想请教下,当A,B线程同时获取锁时,假设当前锁对象是匿名可偏向的,A线程CAS成功了,获取到锁了,B线程失败了,就会进入这里CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);假设是开启了偏向并且没有触发批量重偏向和撤销,最终会启动VM线程执行撤销偏向锁吧,那启动VM线程接下来怎么执行呢?是执行这里UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);再次获取锁么?谢谢了
from myblog.
轻量级锁Lock record的指针地址和重量级锁Object monitor的指针地址都是00结尾的二进制吗?即4的倍数。 本人Java,不懂请教。
from myblog.
你好,有个疑问想请教下,当A,B线程同时获取锁时,假设当前锁对象是匿名可偏向的,A线程CAS成功了,获取到锁了,B线程失败了,就会进入这里CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);假设是开启了偏向并且没有触发批量重偏向和撤销,最终会启动VM线程执行撤销偏向锁吧,那启动VM线程接下来怎么执行呢?是执行这里UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1);再次获取锁么?谢谢了
会执行到这一块代码, 具体的作者也有些 revoke的逻辑在fast_enter里
RT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
...
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
"must be NULL or an object");
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
...
IRT_END```
from myblog.
Related Issues (20)
- 死磕Synchronized底层实现--概论 HOT 71
- 死磕Synchronized底层实现--轻量级锁 HOT 63
- 死磕Synchronized底层实现--重量级锁 HOT 33
- Kafka Network层解析
- Spring Boot @EnableAutoConfiguration解析
- 来谈谈限流-从概念到实现 HOT 2
- 来谈谈限流-RateLimiter源码分析
- 你真的知道什么是系统调用吗?
- 拿下阿里、头条、滴滴的offer后谈谈面试经验(上) HOT 16
- 拿下阿里、头条、滴滴的offer后谈谈面试经验(下) HOT 2
- 将撤销偏向锁的操作放到进入SafePoint时执行,VMThread::execute(&revoke)是会阻塞当前线程的执行吗?
- wait调用描述是否有误
- 资源竞争激烈情况下,为什么ReentrantLock效率高于synchronized
- 轻量锁释放锁后接着另外一个线程线程来加锁的流程
- 关于Lock Record的一些疑问
- JVM源码阅读方式
- 为什么一开始就要分配lock record,lock record不是在轻量级锁才会使用吗 HOT 1
- 请教您以下关于lock record的问题
- 图看不到啊, 裂开了 HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from myblog.