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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听