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

相关文章

JAVA中安装多个JDK的方法

《JAVA中安装多个JDK的方法》文章介绍了在Windows系统上安装多个JDK版本的方法,包括下载、安装路径修改、环境变量配置(JAVA_HOME和Path),并说明如何通过调整JAVA_HOME在... 首先去oracle官网下载好两个版本不同的jdk(需要登录Oracle账号,没有可以免费注册)下载完

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

Spring Boot 结合 WxJava 实现文章上传微信公众号草稿箱与群发

《SpringBoot结合WxJava实现文章上传微信公众号草稿箱与群发》本文将详细介绍如何使用SpringBoot框架结合WxJava开发工具包,实现文章上传到微信公众号草稿箱以及群发功能,... 目录一、项目环境准备1.1 开发环境1.2 微信公众号准备二、Spring Boot 项目搭建2.1 创建

Java中Integer128陷阱

《Java中Integer128陷阱》本文主要介绍了Java中Integer与int的区别及装箱拆箱机制,重点指出-128至127范围内的Integer值会复用缓存对象,导致==比较结果为true,下... 目录一、Integer和int的联系1.1 Integer和int的区别1.2 Integer和in

SpringSecurity整合redission序列化问题小结(最新整理)

《SpringSecurity整合redission序列化问题小结(最新整理)》文章详解SpringSecurity整合Redisson时的序列化问题,指出需排除官方Jackson依赖,通过自定义反序... 目录1. 前言2. Redission配置2.1 RedissonProperties2.2 Red

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

JSONArray在Java中的应用操作实例

《JSONArray在Java中的应用操作实例》JSONArray是org.json库用于处理JSON数组的类,可将Java对象(Map/List)转换为JSON格式,提供增删改查等操作,适用于前后端... 目录1. jsONArray定义与功能1.1 JSONArray概念阐释1.1.1 什么是JSONA

Java JDK1.8 安装和环境配置教程详解

《JavaJDK1.8安装和环境配置教程详解》文章简要介绍了JDK1.8的安装流程,包括官网下载对应系统版本、安装时选择非系统盘路径、配置JAVA_HOME、CLASSPATH和Path环境变量,... 目录1.下载JDK2.安装JDK3.配置环境变量4.检验JDK官网下载地址:Java Downloads

Spring boot整合dubbo+zookeeper的详细过程

《Springboot整合dubbo+zookeeper的详细过程》本文讲解SpringBoot整合Dubbo与Zookeeper实现API、Provider、Consumer模式,包含依赖配置、... 目录Spring boot整合dubbo+zookeeper1.创建父工程2.父工程引入依赖3.创建ap

SpringBoot结合Docker进行容器化处理指南

《SpringBoot结合Docker进行容器化处理指南》在当今快速发展的软件工程领域,SpringBoot和Docker已经成为现代Java开发者的必备工具,本文将深入讲解如何将一个SpringBo... 目录前言一、为什么选择 Spring Bootjavascript + docker1. 快速部署与