JVM源码分析 – 偏向锁

2024-08-21 18:58
文章标签 java 分析 源码 jvm 偏向

本文主要是介绍JVM源码分析 – 偏向锁,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

JVM源码分析 – 偏向锁

前言

JAVA在内部提供了许多种锁,在虚拟机内部,又会根据虚拟机配置和场景来使用不同种类的锁,比如偏向轻量级以及重量级等等,这篇文章根据1.8的源码,来看一下JAVA内部实现的锁。提前说明,笔者并非专业的C++开发人员,只了解一些基本的语法,只能通过方法命名、注释来大体了解一下代码的执行过程以及效果。

我们从字节码开始,我们使用synchronized(Object)包裹代码块的时候,字节码中会用如下方式包裹:

 

1

2

3

 

monitorenter; lock object in local ...

...

monitorexit; finished with object in local ...

解释器执行monitorenter的时候,会进入InterpreterRuntime.cpp,我们直接看一下代码:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

 

//这里这个thread,就是java中的currentThread

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))

#ifdef ASSERT

thread->last_frame().interpreter_frame_verify_monitor(elem);

#endif

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");

//这里这个UseBiasedLocking,是一个全局的运行时配置,标识虚拟机是否开启偏向锁,开启用fast,否则slow

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);

}

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

有点不明白的地方,在Google上找了一下BasicObjectLock的定义,这就是一个包装类:

 

1

2

3

4

5

 

class BasicObjectLock {

BasicLock _lock;

// object holds the lock;

oop _obj;

}

持有一个BasicLock和一个对象。这个BasicLock对象找了一顿定义,没有找到,但是从他的方法来看,主要是跟一些地址偏移相关,同样Google一下,找到了其定义:

 

1

2

3

4

 

class BasicLock {

//主要用来保存_obj指向Object对象的对象头数据

volatile markOop _displaced_header;

}

这个Handle类可以理解成一个“句柄”,他被用来在垃圾回收的时候,对象可能移动(地址变化),通过handle访问对象可以让调用方不去关心地址的变化,垃圾回收的细节。

偏向锁

我们接着看上面的代码,我们发现虚拟机发现开启偏向锁,会优先使用偏向锁,偏向锁只依赖一次CAS操作来置换ThreadId。默认开启了偏向锁,我们可以用-XX:-UseBiasedLocking关闭。

我们看一下上面的fast_enter方法:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

 

void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {

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 (obj, lock, THREAD) ;

}

这里又判断了一次,是怕运行时更新了配置?

我们这里看到了一个新的名词叫SafePoint,这里是个新东西,在Hotspot的垃圾回首时,这个SafePoint也会出现,我们先不考虑,只是发现,如果不在安全点上,我们就进入偏向。我们从revoke_and_rebias这个名字可以看出,偏向锁就是在没有多线程竞争的情况下,减少不必要的锁执行路径,只是更新了一下线程Id(撤销一个,偏向另外一个)

####revoke_and_rebias

这个方法连带注释,有好多好多逻辑,我们只看一下比较主要的,了解一下偏向锁的实现逻辑,先看代码:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

 

BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {

assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");

// We can revoke the biases of anonymously-biased objects

// efficiently enough that we should not cause these revocations to

// update the heuristics because doing so may cause unwanted bulk

// revocations (which are expensive) to occur.

//获取对象头的Mark Word

markOop mark = obj->mark();

//判断mark是否为可偏向状态

if (mark->is_biased_anonymously() && !attempt_rebias) {

// We are probably trying to revoke the bias of this object due to

// an identity hash code computation. Try to revoke the bias

// without a safepoint. This is possible if we can successfully

// compare-and-exchange an unbiased header into the mark word of

// the object, meaning that no other thread has raced to acquire

// the bias of the object.

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()) {

// This object has a stale bias from before the bulk revocation

// for this data type occurred. It's pointless to update the

// heuristics at this point so simply update the header with a

// CAS. If we fail this race, the object's bias has been revoked

// by another thread so we simply return and let the caller deal

// with it.

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()) {

// The epoch of this biasing has expired indicating that the

// object is effectively unbiased. Depending on whether we need

// to rebias or revoke the bias of this object we can do it

// efficiently enough with a CAS that we shouldn't update the

// heuristics. This is normally done in the assembly code but we

// can reach this point due to various points in the runtime

// needing to revoke biases.

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;

}

}

}

}

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()) {

// A thread is trying to revoke the bias of an object biased

// toward it, again likely due to an identity hash code

// computation. We can again avoid a safepoint in this case

// since we are only going to walk our own stack. There are no

// races with revocations occurring in other threads because we

// reach no safepoints in the revocation path.

// Also check the epoch because even if threads match, another thread

// can come in with a CAS to steal the bias of an object that has a

// stale epoch.

ResourceMark rm;

if (TraceBiasedLocking) {

tty->print_cr("Revoking bias by walking my own stack:");

}

BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD);

((JavaThread*) THREAD)->set_cached_monitor_info(NULL);

assert(cond == BIAS_REVOKED, "why not?");

return cond;

} else {

VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);

VMThread::execute(&revoke);

return revoke.status_code();

}

}

assert((heuristics == HR_BULK_REVOKE) ||

(heuristics == HR_BULK_REBIAS), "?");

VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,

(heuristics == HR_BULK_REBIAS),

attempt_rebias);

VMThread::execute(&bulk_revoke);

return bulk_revoke.status_code();

}

第一段注释我大概理解了一下,应该是在说为什么需要偏向锁,因为竞争少,避免批量的竞争,减少开销。

判断的时候我们可以看到调用了is_biased_anonymously,这个方法最后会调用如下方法:

 

1

2

3

4

 

bool has_bias_pattern() const {

//这里biased_lock_pattern值是5,二级制101

return (mask_bits(value(), biased_lock_mask_in_place) == biased_lock_pattern);

}

那我们就知道了,is_biased_anonymously方法判断是否为可偏向状态,而mark的偏向锁标志位为1,锁标识位为01。

第二段注释,我理解了一下,大概是说,使用对象的hashCode计算,如果可以成功的将没有偏向锁的对象头与对象标记进行比较和交换,意味着没有其他线程在获取偏向锁。

说白了,如果没有竞争状态,我们就把mark中的JavaThread设置为当前线程Id,执行成功则执行代码块。

我们看一下,如果这个对象只是有一个偏向锁的头,会怎么样:

 

1

2

3

4

5

6

7

8

9

10

11

12

 

if (!prototype_header->has_bias_pattern()) {

// This object has a stale bias from before the bulk revocation

// for this data type occurred. It's pointless to update the

// heuristics at this point so simply update the header with a

// CAS. If we fail this race, the object's bias has been revoked

// by another thread so we simply return and let the caller deal

// with it.

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;

}

这里是说如果撤销之前有一个过时的偏向锁,更新是没有意义的,会返回并交还给另外的线程去执行。

后面JVM会执行一段代码:HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);,这段代码看不懂,只能再次去查了一下,笨神给出了比较详尽的解释:

update_heuristics的方法真正作用主要是当cas失败达到一定次数之后来决定是否批量偏向或者去偏向。在我们这段代码中,执行到这里有以下几种情况:

  1. 对象不是偏向模式
  2. 对象是偏向模式但是epoch过期了,如下两种情况下才会执行到这里
  • 打算重偏向,但是偏向了另外的线程
  • 打算撤销偏向

说白了,我们可能会已经一个偏向锁过期的问题,还可能存在synchronized(object)中这个object被回收的问题!这就很神奇了,按照我的理解,这个object应该不会被回收才对。也有可能是加锁过程中回收了。我找了一下Hotspot的的文档中,关于锁的部分,看到其中有这么一句话:

An epoch value in the class acts as a timestamp that indicates the validity of the bias. This value is copied into the header word upon object allocation. Bulk rebiasing can then efficiently be implemented as an increment of the epoch in the appropriate class. The next time an instance of this class is going to be locked, the code detects a different value in the header word and rebiases the object towards the current thread.

上面这段话解释了,对象存在过期的情况,标识符就是epoch。文章最后会附上一份MarkWord的存储状态图,在markOop.cpp中,有兴趣的可以看一下。

再下面的代码就不看了,我们可以在后面看到线程执行了(VMThread::execute(&bulk_revoke);),我们重新回到fast_enter中,如果我们执行失败了会怎么办,这里会撤销偏向锁,执行方法:BiasedLocking::revoke_at_safepoint(obj);,再往下,我们执行会进入到slow_enter中,进入之后我们的锁升级为轻量级锁。

偏向锁的撤销

 

1

2

3

4

5

6

7

8

9

10

11

12

 

void BiasedLocking::revoke_at_safepoint(Handle h_obj) {

assert(SafepointSynchronize::is_at_safepoint(), "must only be called while at safepoint");

oop obj = h_obj();

HeuristicsResult heuristics = update_heuristics(obj, false);

if (heuristics == HR_SINGLE_REVOKE) {

revoke_bias(obj, false, false, NULL);

} else if ((heuristics == HR_BULK_REBIAS) ||

(heuristics == HR_BULK_REVOKE)) {

bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);

}

clean_up_cached_monitor_info();

}

撤销偏向锁我们又看到了SafePoint,我们的撤销动作,需要等待一个安全点。这是我们需要暂停一下拥有偏向锁的线程,判断对象是否处于被锁定的状态,然后撤销锁,恢复到无锁或轻量级锁的状态(bulk_revoke_or_rebias_at_safepoint)。

小Tips:

偏向锁在Java 1.6之后是默认启用的,但在应用程序启动几秒钟之后才激活,可以使用-XX:BiasedLockingStartupDelay=0参数关闭延迟,如果确定应用程序中所有锁通常情况下处于竞争状态,可以通过XX:-UseBiasedLocking=false参数关闭偏向锁,我们根据上面的代码可以知道,关闭偏向锁可以节省一部分开销,绕过很多操作直接进入到轻量级锁处理中。

感谢笨神和小狼大神的分享和解答。

附:

 

1

2

3

4

5

6

7

8

 

// Bit-format of an object header (most significant first, big endian layout below):

//

// 32 bits:

// --------

// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)

// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)

// size:32 ------------------------------------------>| (CMS free block)

// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)

这篇关于JVM源码分析 – 偏向锁的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1094006

相关文章

Spring Boot项目部署命令java -jar的各种参数及作用详解

《SpringBoot项目部署命令java-jar的各种参数及作用详解》:本文主要介绍SpringBoot项目部署命令java-jar的各种参数及作用的相关资料,包括设置内存大小、垃圾回收... 目录前言一、基础命令结构二、常见的 Java 命令参数1. 设置内存大小2. 配置垃圾回收器3. 配置线程栈大小

SpringBoot实现微信小程序支付功能

《SpringBoot实现微信小程序支付功能》小程序支付功能已成为众多应用的核心需求之一,本文主要介绍了SpringBoot实现微信小程序支付功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作... 目录一、引言二、准备工作(一)微信支付商户平台配置(二)Spring Boot项目搭建(三)配置文件

解决SpringBoot启动报错:Failed to load property source from location 'classpath:/application.yml'

《解决SpringBoot启动报错:Failedtoloadpropertysourcefromlocationclasspath:/application.yml问题》这篇文章主要介绍... 目录在启动SpringBoot项目时报如下错误原因可能是1.yml中语法错误2.yml文件格式是GBK总结在启动S

Spring中配置ContextLoaderListener方式

《Spring中配置ContextLoaderListener方式》:本文主要介绍Spring中配置ContextLoaderListener方式,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录Spring中配置ContextLoaderLishttp://www.chinasem.cntene

java实现延迟/超时/定时问题

《java实现延迟/超时/定时问题》:本文主要介绍java实现延迟/超时/定时问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java实现延迟/超时/定时java 每间隔5秒执行一次,一共执行5次然后结束scheduleAtFixedRate 和 schedu

Java Optional避免空指针异常的实现

《JavaOptional避免空指针异常的实现》空指针异常一直是困扰开发者的常见问题之一,本文主要介绍了JavaOptional避免空指针异常的实现,帮助开发者编写更健壮、可读性更高的代码,减少因... 目录一、Optional 概述二、Optional 的创建三、Optional 的常用方法四、Optio

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

Spring Boot项目中结合MyBatis实现MySQL的自动主从切换功能

《SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能》:本文主要介绍SpringBoot项目中结合MyBatis实现MySQL的自动主从切换功能,本文分步骤给大家介绍的... 目录原理解析1. mysql主从复制(Master-Slave Replication)2. 读写分离3.

idea maven编译报错Java heap space的解决方法

《ideamaven编译报错Javaheapspace的解决方法》这篇文章主要为大家详细介绍了ideamaven编译报错Javaheapspace的相关解决方法,文中的示例代码讲解详细,感兴趣的... 目录1.增加 Maven 编译的堆内存2. 增加 IntelliJ IDEA 的堆内存3. 优化 Mave

Java String字符串的常用使用方法

《JavaString字符串的常用使用方法》String是JDK提供的一个类,是引用类型,并不是基本的数据类型,String用于字符串操作,在之前学习c语言的时候,对于一些字符串,会初始化字符数组表... 目录一、什么是String二、如何定义一个String1. 用双引号定义2. 通过构造函数定义三、St