Giter Site home page Giter Site logo

Comments (78)

farmerjohngit avatar farmerjohngit commented on August 19, 2024 9

@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.

aLibeccio avatar aLibeccio commented on August 19, 2024 6
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.

weiaiSanli avatar weiaiSanli commented on August 19, 2024 4

epoch在markWord中占用的是2bit位,也就是最大值为11 = 3,它是如何被增加到40以后修改成去掉偏向锁的呢?这个是bit不是byte呀!

from myblog.

farmerjohngit avatar farmerjohngit commented on August 19, 2024 1

@yinjk 关于第一个问题,应该是说:有且只有调用revoke_and_rebias的线程被重偏向为另一个线程了,返回的才是BiasedLocking::BIAS_REVOKED_AND_REBIASE(大致扫了下代码,应该是这样)。然后决定bulk_revoke.status_code()值的代码在这里VMThread::execute得主要作用是将对应方法(bulk_revoke所在类的doit)放到VM Thread中执行,关于VM Thread的介绍可以看这里

from myblog.

yinjk avatar yinjk commented on August 19, 2024

请问一下作者,文中偏向锁的撤销小节中,当线程A已经退出同步代码块了,此时线程B请求对象锁触发偏向锁撤销流程,锁对象根据是否允许重偏向被撤销为匿名偏向状态或无锁状态,若允许重偏向,线程B能直接获取到偏向锁吗,是否允许重偏向是如何判断的呢,期待解惑,万分感谢

from myblog.

farmerjohngit avatar farmerjohngit commented on August 19, 2024

@yinjk 当B请求锁时,如果没有触发到批量重偏向的阈值(update_heuristics方法),那会被直接撤销为无锁状态。而如果到到达了重偏向的阈值,则会触发批量重偏向,逻辑在bulk_revoke_or_rebias_at_safepoint方法中,首先会自增类中的epoch(代码),然后会调用revoke_bias(代码),且第二个参数allow_rebias为true。如果A已经退出同步块了但是还存活,则在revoke_bias中的这里,会进行重偏向。

有不明白的地方可以再留言

from myblog.

yinjk avatar yinjk commented on August 19, 2024

感谢解答,文章写得很好很清楚,基本上理解了,但是还有一点点疑问,在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.

yinjk avatar yinjk commented on August 19, 2024

非常感谢作者耐心解答,根据指示,问题已经得到解决。

from myblog.

zyllt avatar zyllt commented on August 19, 2024

感谢,最近一直在学习偏向锁方面的知识,自己也看了源码还是无法理解一些细节,拜读了此文之后疑问全解,感觉浑身通透

from myblog.

farmerjohngit avatar farmerjohngit commented on August 19, 2024

@zyllt 有帮助就好~

from myblog.

zyllt avatar zyllt commented on August 19, 2024

@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.

farmerjohngit avatar farmerjohngit commented on August 19, 2024

@zyllt
首先从概念上来说只有批量重偏向,没有单个重偏向。批量重偏向需要用到VMThread,因为要操作其他线程的栈,所以需要在借助VMThread在safepopint中执行。

对于非批量重偏向的步骤如下:
1.如果线程A还存活且还在同步块中,则将锁对象升级为轻量级锁,同时需要修改线程A的栈,代码在这里。线程A不存活或不在同步块中,则先将锁对象设置为无锁状态,代码在这里

2.在之后的slow_enter中,如果锁对象是无锁状态,则会升级为轻量级锁。否则升级为重量级锁。

也就是说,如果一个锁已经偏向线程A,当线程B尝试获得该锁时,无论线程A是什么状态,该锁都会升级成轻量级锁或重量级锁(不考虑批量重偏向的情况)。

如果你还不太理解,建议先看下上篇文章补充下概念上的知识。

from myblog.

zyllt avatar zyllt commented on August 19, 2024

@farmerjohngit 感谢回答,确实是我理解错了。

from myblog.

ValiantYuan avatar ValiantYuan commented on August 19, 2024

您的文章写的非常详细,受教了!我有一个问题,始终没有明白,在偏向锁入口code10,为什么在这个逻辑里处理偏向锁重入
// 如果偏向线程不是当前线程或没有开启偏向模式等原因都会导致success==false
if (!success) {
不是只有对同一个线程来讲才有锁重入的概念吗?既然如此,为什么偏向线程不是当前线程呢?还是我对锁重入的概念理解错了?麻烦您能指导一下,这个问题困惑我好久了,谢谢!

from myblog.

farmerjohngit avatar farmerjohngit commented on August 19, 2024

@ValiantYuan 走到这里说明已经是轻量级锁的逻辑了

from myblog.

ValiantYuan avatar ValiantYuan commented on August 19, 2024

@farmerjohngit

@ValiantYuan 走到这里说明已经是轻量级锁的逻辑了

谢谢,明白了。

from myblog.

DanFL avatar DanFL commented on August 19, 2024

您好,我想请教一下,假如偏向锁持所线程Thread A已经不存活了,Thread B触发VMThread将偏向锁重置为匿名偏向后,Thread B在又何时重新CAS获取到偏向锁?

from myblog.

farmerjohngit avatar farmerjohngit commented on August 19, 2024

@DanFL 可以看上面对 @zyllt 的回答:

也就是说,如果一个锁已经偏向线程A,当线程B尝试获得该锁时,无论线程A是什么状态,该锁都会升级成轻量级锁或重量级锁(不考虑批量重偏向的情况)。

所以B普通情况下不会再获得该偏向锁。

而升级轻量级锁或重量级锁的逻辑在这里

from myblog.

DanFL avatar DanFL commented on August 19, 2024

是指只要Thread B在HR_SINGLE_REVOKE case下,因为revoke_bias()方法传的allow_rebias都是false,所以不会重偏向

@DanFL 可以看上面对 @zyllt 的回答:

也就是说,如果一个锁已经偏向线程A,当线程B尝试获得该锁时,无论线程A是什么状态,该锁都会升级成轻量级锁或重量级锁(不考虑批量重偏向的情况)。

所以B普通情况下不会再获得该偏向锁。

而升级轻量级锁或重量级锁的逻辑在这里

是指只要Thread B在HR_SINGLE_REVOKE case下,因为revoke_bias()方法传的allow_rebias都是false,所以不会重偏向?

还有几个问题也想一并请教一下

  1. epoch这个值,每次都是自增都是发生在safepoint里执行的,假如锁对象epoch过期,也只是将类对象中的epoch值更新到锁对象中。那锁对象中的epoch值有何作用?并没有在其他处用到,假如是为了记录锁批量重偏向次数,只保留类对象中的epoch不就可以了么?
  2. prototype_header是存在类对象当中的,类对象本身作为对象应该也有属于自己的MardWord,是么?若是的话,这两者之间有什么区别么?
  3. 关于批量重偏向,依我的理解,假如Thread B获取所,第一次发生锁撤销,revocation_count从0到1,就进入HR_SINGLE_REVOKE case进行锁升级了。如果不考虑重置revocation_count,同一个类的锁对象只有第20次到40次之间的锁撤销才有可能重偏向
  4. 下述中提到epoch值过期也要进入到锁撤销,而不是CAS重偏向失败才进入的么?

以上是偏向锁加锁的流程(包括部分轻量级锁的加锁流程),如果当前锁已偏向其他线程||epoch值过期||偏向模式关闭||获取偏向锁的过程中存在并发冲突,都会进入到InterpreterRuntime::monitorenter方法, 在该方法中会对偏向锁撤销和升级。
中提到epoch值过期也要进入到锁撤销,而不是CAS重偏向失败才进入的么?

from myblog.

farmerjohngit avatar farmerjohngit commented on August 19, 2024

@DanFL 最近比较忙,你的问题我慢慢回答哈

是指只要Thread B在HR_SINGLE_REVOKE case下,因为revoke_bias()方法传的allow_rebias都是false,所以不会重偏向?

from myblog.

farmerjohngit avatar farmerjohngit commented on August 19, 2024

epoch这个值,每次都是自增都是发生在safepoint里执行的,假如锁对象epoch过期,也只是将类对象中的epoch值更新到锁对象中。那锁对象中的epoch值有何作用?并没有在其他处用到,假如是为了记录锁批量重偏向次数,只保留类对象中的epoch不就可以了么?

epoch的作用是在触发批量重偏向阈值后,下次获得锁时,通过判断class的epoch和obj的epoch决定要不要直接重偏向。 这点在本文中也说过了,建议你将本文开头处的monitorenterrevoke_and_rebias相关代码在看一遍,我在注释中都有说明

from myblog.

farmerjohngit avatar farmerjohngit commented on August 19, 2024

下述中提到epoch值过期也要进入到锁撤销,而不是CAS重偏向失败才进入的么?

这里我写的有问题,是重偏向失败才进入

from myblog.

farmerjohngit avatar farmerjohngit commented on August 19, 2024

prototype_header是存在类对象当中的,类对象本身作为对象应该也有属于自己的MardWord,是么?若是的话,这两者之间有什么区别么?

如果你说的是Java层的类对象,那是的,因为类对象本身也是对象。至于你问的区别,markword是对象的(包括类对象),Native层对应的是oop->mark();而prototype_header是类的,Native层的klass->prototype_header()

如果你说的是Native层的Klass,那不是的,klass.hpp中并没有markword只有prototype_header

from myblog.

pigeonsoar avatar pigeonsoar commented on August 19, 2024

水平不够 看源码实在是受不住啊 全靠注释看下来了 有个小问题 在锁升级的时候:

将偏向线程所有相关Lock Record的Displaced Mark Word设置为null,然后将最高位的Lock Record的Displaced Mark Word 设置为无锁状态,最高位的Lock Record也就是第一次获得锁时的Lock Record(这里的第一次是指重入获取锁时的第一次)

偏向锁重入也会插入一次Lock Record嘛?那为什么上面偏向锁重入那里的注释是什么也没做呢?
然后是括号里的内容 为什么是一次重入获取的锁 第一次偏向的时候不就分配了一个Lock Record嘛 为什么用的是重入加的Lock Record呢?

from myblog.

farmerjohngit avatar farmerjohngit commented on August 19, 2024

@pigeonsoar

偏向锁重入也会插入一次Lock Record嘛?那为什么上面偏向锁重入那里的注释是什么也没做呢?
code1 和 code2处已经拿到一个空闲的Lock Record,然后指向当前锁对象了。

from myblog.

farmerjohngit avatar farmerjohngit commented on August 19, 2024

然后是括号里的内容 为什么是一次重入获取的锁 第一次偏向的时候不就分配了一个Lock Record嘛 为什么用的是重入加的Lock Record呢?

不太记得了 不排除我搞错了

from myblog.

May0302 avatar May0302 commented on August 19, 2024

如果要撤销的锁偏向的不是当前线程,会将该操作push到VM Thread中等到safepoint的时候再执行。
等待safepoint的这个过程中,当前线程是继续往下走到slow_enter方法里面吗?还是阻塞直到升级成为轻量级锁?

from myblog.

farmerjohngit avatar farmerjohngit commented on August 19, 2024

@May0302 在VM_RevokeBias的模式下,VMThread::execute是阻塞的

from myblog.

wanghaolong avatar wanghaolong commented on August 19, 2024

如果一个系统都用object类作为锁,是不是很可能导致这个object锁被撤销?

from myblog.

scn7th avatar scn7th commented on August 19, 2024

看了您的文章受益匪浅,但是也有以下几个问题,期待您的解答,谢谢!

  1. 每次进入monitor_enter就创建一个lock record是哪段代码,没找到啊
    2.偏向锁释放后lock record怎么办,就留在栈帧中吗?
  2. 偏向锁升级时发现当前持有当前对象的线程已经不再使用,那么会将其置为无锁状态,然后升级,此时这个无锁状态中有哪些字段?还有hashcode吗?
  3. 轻量级锁解锁后,用cas替换对象的markword为栈帧中的displaced mark word,此时displaced mark word应该是锁升级前的,那么此时轻量级锁是不是就完全释放,下一次加锁前是偏向/无锁状态,而不是直接进入轻量级锁?这样不就是锁降级了?

from myblog.

scn7th avatar scn7th commented on August 19, 2024

还有一个问题,对象mark word中的锁标志位是在哪块修改的,只看到CAS修改指针地址,没看到对锁标志位修改,谢谢!

from myblog.

java4lyvee avatar java4lyvee commented on August 19, 2024

比如程序员如何知道锁的状态呢?有没有办法直观看到某个时候锁的状态,比是不是偏向锁,是不是重量级锁?

from myblog.

lailvliang avatar lailvliang commented on August 19, 2024

看了受益匪浅!!有个问题想问问
在批量重偏向的时候会对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.

hzhswyz avatar hzhswyz commented on August 19, 2024

将撤销偏向锁的操作放到进入SafePoint时执行,VMThread::execute(&revoke)是会阻塞当前线程的执行吗?
VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);
VMThread::execute(&revoke);
return revoke.status_code();

from myblog.

WLWL avatar WLWL commented on August 19, 2024

会走到该方法的逻辑有很多,我们只分析最常见的情况:假设锁已经偏向线程A,这时B线程尝试获得锁。

上面的code 1,code 2B线程都不会走到,最终会走到code 4处,如果要撤销的锁偏向的是当前线程则直接调用revoke_bias撤销偏向锁,否则会将该操作push到VM Thread中等到safepoint的时候再执行。

这里的当前线程 指的是线程A? @farmerjohngit

from myblog.

 avatar commented on August 19, 2024

偏向锁是在 同步代码块之后释放吗?

from myblog.

baixinping0 avatar baixinping0 commented on August 19, 2024

你好,对象头中,轻量级锁的标识是 00 ,为什升级为轻量级锁的时候,却把Displaced header设置为锁状态的 01,这样轻量级锁释放的时候,对象头不是变成01(无锁)了吗。不应该是00(轻量级锁)吗

from myblog.

FishInJava avatar FishInJava commented on August 19, 2024

大神你好,看了很多遍,受益匪浅,奈何实在不懂C++代码。我想请教下,Lock Record是在执行_monitorenter前就已经在栈中创建好了吗?C++代码中没看到有这块的内容。当然你的博客中有提及

from myblog.

dslztx avatar dslztx commented on August 19, 2024

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.

aLibeccio avatar aLibeccio commented on August 19, 2024

在调用 hashcode 方法时轻量级锁也需要锁升级吗?我理解轻量级锁里 Lock Word 的 displaced mark word 不是可以存 hashcode 嘛

from myblog.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

Lock Record是在执行_monitorenter前就已经在栈中创建好了吗?C++代码中没看到有这块的内容。当然你的博客中有提及

是在私有线程栈找到一个最近并且空闲的区域,创建一个Lock Record,里面属性为null。
BasicObjectLock* limit = istate->monitor_base();
BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base();
BasicObjectLock* entry = NULL;

from myblog.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

你好,对象头中,轻量级锁的标识是 00 ,为什升级为轻量级锁的时候,却把Displaced header设置为锁状态的 01,这样轻量级锁释放的时候,对象头不是变成01(无锁)了吗。不应该是00(轻量级锁)吗

轻量级锁释放的时候,即跳出临界区,要把对象设置为无锁的状态

from myblog.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

没有办法直观看到某个时候锁的状态,比是不是偏向锁,是不是重量级锁
在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.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024
<!--sun公司 专门解析对象实例,查看对象布局-->
<dependency>
 <groupId>org.openjdk.jol</groupId>
  <artifactId>jol-core</artifactId>
  <version>0.9</version>
</dependency>

from myblog.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

你好,作者,有一些问题,不是很明白。
假设偏向延迟关闭的情况下,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.

RecordaLi avatar RecordaLi commented on August 19, 2024

想请问一下:
(1)当线程第一次获得偏向锁的时候,是否会在栈中添加一个空的Lock Record,若不会,则当其他线程来竞争时才会往持有锁的线程添加Lock Record吗?否则,怎么进行升级轻量级锁。
【我的理解:要添加一个空的Lock Record,其中只设置obj指向锁的对象】

(2)当偏向锁重入的时候,是否要继续添加Lock Record?
【我的理解,同样需要,Lock Record和第一次添加的相同,若不添加Lock Record怎么确定锁重入的次数】

from myblog.

RecordaLi avatar RecordaLi commented on August 19, 2024

是的。要添加一个空的Lock Record,其中只设置obj指向锁的对象。但是在不同的栈帧里面。synchronized{        //1      synchronized{          //2      }   //3  }  //4 只要进入 synchronized 对应 _monitorenter,只要退出synchronized 对应 _monitorexit;所以两个Lock Record 并没有冲突的。

噢,感谢作者!我大概明白了,总的来说就是:

  1. 如果偏向锁第一次进入,经过所有检测后,会在当前线程栈帧中找到内存地址最高的可用Lock Record,Displaced Mark Word会将线程ID存入,同时obj指向对象,并修改对象头为线程ID

  2. 如果是偏向锁重入,则只会添加一个DMW为空的Lock Record,其中obj还是会指向对象

  3. 当偏向锁释放时,只会从底到高,将obj相符的Lock Record的obj设置为null,不会改变对象Mark Word的threadId

from myblog.

RecordaLi avatar RecordaLi commented on August 19, 2024

最后一个小疑问就是,我在代码中似乎没看到偏向锁重入时会添加一个空的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.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

from myblog.

xdcode2020 avatar xdcode2020 commented on August 19, 2024

在阅读偏向锁的源码后,自己对知识做了测试验证,其中下面有一个理解上面的确认 和 还有一个没有理解清楚的疑问,下面先说明一下测试的情况:

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.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

@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.

xdcode2020 avatar xdcode2020 commented on August 19, 2024

使用博主你的代码,我测试: 在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版本:

image-20201204182154004

测试代码(使用的博主的):

	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

@wangliyu0328

from myblog.

xdcode2020 avatar xdcode2020 commented on August 19, 2024

@wangliyu0328
至于你说的第二个问题的分析原因,我之前也考虑过。 特意打印过线程ID,是2个不同的线程ID。 而且不是机率问题,是每次都是发生了重偏向

from myblog.

xdcode2020 avatar xdcode2020 commented on August 19, 2024

你好,作者,有一些问题,不是很明白。
假设偏向延迟关闭的情况下,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.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

@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.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

@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.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

@baixinping0 升级为轻量锁前,就是把Displaced header设置为锁状态的 01,轻量级锁释放的时候,对象头变成01(无锁)了。轻量锁升级的时候,CAS把锁标识设置为00。但轻量锁释放的时候,为01的。

from myblog.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

@weiaiSanli

epoch在markWord中占用的是2bit位,也就是最大值为11 = 3,它是如何被增加到40以后修改成去掉偏向锁的呢?这个是bit不是byte呀!
是通过一个 int revocation_count变量,为该类偏向锁撤销的次数。代码 在这里
默认值为40,在globals.hpp里设置的。

from myblog.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

@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.

DZQOX avatar DZQOX commented on August 19, 2024

@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.

HenryChenV avatar HenryChenV commented on August 19, 2024

@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是在哪里更新的?

  1. 偏向锁偏向某个线程时不会修改prototype_header
  2. 偏向锁的重入和LockRecord有关,不知道问题在哪
  3. klass的prototype_header只会在批量重偏向和批量撤销时修改

我也看了下这块的代码,可以参考下这个:https://github.com/HenryChenV/my-notes#synchronized%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90

from myblog.

DZQOX avatar DZQOX commented on August 19, 2024

@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是在哪里更新的?

  1. 偏向锁偏向某个线程时不会修改prototype_header
  2. 偏向锁的重入和LockRecord有关,不知道问题在哪
  3. 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.

DanchuoZhong avatar DanchuoZhong commented on August 19, 2024

你好,有一个小的细节问题,想要请教一下。在锁撤销部分的偏向标志被关闭时的代码(就是这段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.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

@DanchuoZhong 偏向锁批量撤销 :当一个线程撤销次数20次时,会统一把这个对象 A a的类, 只要含有A这个类的锁,都撤销为无锁状态。CAS在构造header时需要去取prototype_header的相关信息,是去取类的信息,而不仅仅是对象a 的锁信息。lockeet->mark_addr()是取是对象的mark地址。

from myblog.

XHxin avatar XHxin commented on August 19, 2024

请教大佬一个问题。
现在有一个场景:线程t1加锁然后释放(偏向锁),然后t2来加锁再释放(轻量锁),然后t3来加锁(轻量锁)。
当t2释放的时候,mark word应该是如下图紫色部分吧,接着t3来加锁
image
t3来加锁执行到源码中(如下图)的这个判断的时候,是会返回true吗?
image
我请教了别人,得到的回复如下图(红框部分),这使我很困惑。
image

如果t3(轻量锁)来加锁,这个判断是true的话,那它接着执行里面的多个判断,在当前场景下里面的if和else if不是都不成立吗?接着会执行else代码块
image
image

请大神解惑!感谢~

from myblog.

wangliyu0328 avatar wangliyu0328 commented on August 19, 2024

@XHxin
你好,对于mark->has_bias_pattern()是否为true,含义是 Java有没有把偏向延迟禁用;因为在dk6默认开启偏向锁,即程序刚启动创建的对象是不会开启偏向锁的,几秒后后创建的对象才会开启偏向锁的;
作者这里是假设禁用偏向延迟,如果是禁用偏向延迟,if(mark->has_bias_pattern()) 是为ture;
//关闭延迟开启偏向锁 -XX:BiasedLockingStartupDelay=0

有一个场景:线程t1加锁然后释放(偏向锁),然后t2来加锁再释放(轻量锁),然后t3来加锁(轻量锁);
t3进入
success=false,走轻量锁逻辑。

from myblog.

WaitAKennyJ avatar WaitAKennyJ commented on August 19, 2024

开局就没看懂= =
//entry不为null,代表还有空闲的Lock Record
为啥需要空闲的LockRecord

from myblog.

divisionblur avatar divisionblur commented on August 19, 2024

偏向锁Lock Record怎么生成的呀

from myblog.

hhhOscar avatar hhhOscar commented on August 19, 2024

请问代码注释里success==false的逻辑是不是有点问题?我的理解是只有对象头不是偏向锁状态或者class标记为不可偏向,才会导致success==false;如果偏向其他线程,虽然会调用monitorenter,但是success被修改成true,不会重新走success==false的流程

from myblog.

CuriousRookie avatar CuriousRookie commented on August 19, 2024

请问代码注释里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.

stu-xisen avatar stu-xisen commented on August 19, 2024

看了文章有两个地方一直困惑我,麻烦请教一下:
lock record最开始是什么时候被put 到线程栈的?
偏向锁从内存低位置向上寻找空闲lock record,什么情况下会找不到?找不到的话会怎样?

from myblog.

smartisanyyh avatar smartisanyyh commented on August 19, 2024

必须给个👍 透彻!

from myblog.

Evanhelovex avatar Evanhelovex commented on August 19, 2024

你好,有个疑问想请教下,当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.

lloveCat avatar lloveCat commented on August 19, 2024

轻量级锁Lock record的指针地址和重量级锁Object monitor的指针地址都是00结尾的二进制吗?即4的倍数。 本人Java,不懂请教。

from myblog.

JIAMING-LI avatar JIAMING-LI commented on August 19, 2024

你好,有个疑问想请教下,当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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.