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

相关文章

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机

SpringBoot实现动态插拔的AOP的完整案例

《SpringBoot实现动态插拔的AOP的完整案例》在现代软件开发中,面向切面编程(AOP)是一种非常重要的技术,能够有效实现日志记录、安全控制、性能监控等横切关注点的分离,在传统的AOP实现中,切... 目录引言一、AOP 概述1.1 什么是 AOP1.2 AOP 的典型应用场景1.3 为什么需要动态插

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf

Java访问修饰符public、private、protected及默认访问权限详解

《Java访问修饰符public、private、protected及默认访问权限详解》:本文主要介绍Java访问修饰符public、private、protected及默认访问权限的相关资料,每... 目录前言1. public 访问修饰符特点:示例:适用场景:2. private 访问修饰符特点:示例:

详解Java如何向http/https接口发出请求

《详解Java如何向http/https接口发出请求》这篇文章主要为大家详细介绍了Java如何实现向http/https接口发出请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用Java发送web请求所用到的包都在java.net下,在具体使用时可以用如下代码,你可以把它封装成一

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma