Skip to content

Commit

Permalink
markword to synchronized 2
Browse files Browse the repository at this point in the history
  • Loading branch information
yoa1226 committed Nov 6, 2024
1 parent 06e52a6 commit b735be2
Show file tree
Hide file tree
Showing 2 changed files with 299 additions and 3 deletions.
302 changes: 299 additions & 3 deletions _posts/2024-10-30-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ Mark Word (normal):
(Unused) (Hash Code) (GC Age)(Tag)
```

markword 在 JVM 中主要有三个作用:

1. 垃圾回收 —— 存储转发指针并跟踪对象的年龄
3. 锁 —— 存储与轻量级和重量级锁相关的信息
4. 哈希码 —— 存储对象一旦计算出的稳定标识哈希码


对象创建后获取 markword 的值,见[源码](/assets//synchronized//JOLSample_01_Basic.java)

```java
Expand Down Expand Up @@ -209,6 +216,32 @@ void oopDesc::incr_age() {
}
```

## Compact Object Headers

[JEP 450: Compact Object Headers (Experimental)](https://bugs.openjdk.org/browse/JDK-8294992) 对 Java 对象头进行优化,并且将在 OpenJDK 24 发布试验版本。

JEP 450 旨在将 64 位平台下对象 16 字节或者 12 字节(开启指针压缩)的对象头缩减为 8 字节。在 Java 中,大多数都是小对象,缩减对象头能降低 Java 程序占用内存近 20%。

```cpp
//Compact object headers
Header (compact):
64 42 11 7 3 0
[CCCCCCCCCCCCCCCCCCCCCCHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHVVVVAAAASTT]
(Compressed Class Pointer) (Hash Code) /(GC Age)^(Tag)
(Valhalla-reserved bits) (Self Forwarded Tag)
```
上面是 JEP 450 实现的对象头结构,将 markword 和 class pointer 合并成 8 字节,因此不支持 synchronized 传统的基于 markword 的轻量级锁(也叫栈锁)和重量级锁。
> This is problematic for compact object headers because it overwrites the object header and thus loses crucial type information. Therefore, compact object headers are not compatible with legacy locking. If the JVM is configured to run with both legacy locking and compact object headers then compact object headers are disabled.
新的 synchronized 锁可以参考:
1. 轻量级锁- [8291555: Replace stack-locking with fast-locking](https://github.com/openjdk/jdk/pull/10590)
2. 轻量级锁可重入- [Recursive lightweight locking](https://bugs.openjdk.org/browse/JDK-8319796)
3. 重量级锁- [8315884: New Object to ObjectMonitor mapping ](https://github.com/openjdk/jdk/pull/20067)
## 栈帧
看过一些资料的读者应该都知道 `synchronized` 轻量级锁需要借助于 Java 栈来实现。首先是将锁对象的 markword 拷贝到栈中,称之为 Lock Record 的区域,Lock Record 也会有指向对象,然后利用 CAS 操作将对象的 markword 指向 Lock Record。
Expand Down Expand Up @@ -389,12 +422,275 @@ intptr_t* _stack_base; // base of expression stack
// [frame initial esp ] ( == &monitors[0], initially here) initial_sp_offset
```

//todo
后文将传统的轻量级栈锁称之为 LM_LEGACY, 新的轻量级锁称之为 LM_LIGHTWEIGHT,前者通过线程栈实现,后者通过数据结构的栈来实现。

### LM_LEGACY

LM_LEGACY 是通常在网上看到的,基于 Java 栈实现的轻量级锁。它的实现有三个步骤:

1. 保存锁对象到 `BasicObjectLock` 类型的 entry 中。
2. 保存锁对象的 markword 到 entry 中。
3. 使用 CAS 覆盖锁对象的 markword 为 entry 的地址。

```cpp
entry->set_obj(lockee);
if (LockingMode == LM_LEGACY) {
// Traditional fast locking.
markWord displaced = lockee->mark().set_unlocked();
entry->lock()->set_displaced_header(displaced);
success = true;
if (lockee->cas_set_mark(markWord::from_pointer(entry), displaced) != displaced) {
// Is it simple recursive case?
if (THREAD->is_lock_owned((address) displaced.clear_lock_bits().to_pointer())) {
entry->lock()->set_displaced_header(markWord::from_pointer(nullptr));
} else {
success = false;
}
}
if (success) {
THREAD->inc_held_monitor_count();
}
}
```
这里要注意,由于字节对齐的限制存在,比如 `BasicObjectLock` 在 monitors 数组中的位置都是 8 字节对齐的,那么它们地址的最低三位都是 0,也就是说最后两位的标志位也都是 0,正好满足标识为 00 时表示轻量级锁。
```cpp
// A BasicObjectLock associates a specific Java object with a BasicLock.
// It is currently embedded in an interpreter frame.
// Because some machines have alignment restrictions on the control stack,
// the actual space allocated by the interpreter may include padding words
// after the end of the BasicObjectLock. Also, in order to guarantee
// alignment of the embedded BasicLock objects on such machines, we
// put the embedded BasicLock at the beginning of the struct.
class BasicObjectLock
```

### LM_LIGHTWEIGHT

[[REDO] Change LockingMode default from LM_LEGACY to LM_LIGHTWEIGHT](https://bugs.openjdk.org/browse/JDK-8319251) 已经将 `LockingMode` 默认值改成 `LM_LIGHTWEIGHT`,并且后面将移除对 `LM_LEGACY` 的支持。

> In the next release, we plan to change the default of the LockingMode
flag from LM_LEGACY to LM_LIGHTWEIGHT. Project Lilliput needs
the LockingMode flag to be LM_LIGHTWEIGHT. In a release sometime
after the next release, we place to remove support for LM_LEGACY.

```cpp
product(int, LockingMode, LM_LIGHTWEIGHT, \
"(Deprecated) Select locking mode: " \
"0: (Deprecated) monitors only (LM_MONITOR), " \
"1: (Deprecated) monitors & legacy stack-locking (LM_LEGACY), " \
"2: monitors & new lightweight locking (LM_LIGHTWEIGHT, default)") \
range(0, 2) \
```
`LightweightSynchronizer::enter` 方法是 `LM_LIGHTWEIGHT` 的实现,也包含了新版基于 `hash table` 实现的重量级锁,将在 JDK 24 中发布。
```cpp
//->InterpreterRuntime::monitorenter
inline void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, JavaThread* current) {
if (LockingMode == LM_LIGHTWEIGHT) {
LightweightSynchronizer::enter(obj, lock, current);
} else {
enter_legacy(obj, lock, current);
}
}
```

下图是新版 synchronized 获取锁的逻辑。

<image src="/assets/synchronized/new-lock-process.png"/>


#### Value-Based Classes

`@jdk.internal.ValueBased` 注解修饰的类称为 `Value-Based Classes`,详见 [JEP 390: Warnings for Value-Based Classes](https://bugs.openjdk.org/browse/JDK-8249100) 。通常包括下面的类:

- The primitive wrapper classes in java.lang;

- The class java.lang.Runtime.Version;

- The "optional" classes in java.util: Optional, OptionalInt, OptionalLong, and OptionalDouble;

- Many classes in the java.time API: Instant, LocalDate, LocalTime, LocalDateTime, ZonedDateTime, ZoneId, OffsetTime, OffsetDateTime, ZoneOffset, Duration, Period, Year, YearMonth, and MonthDay, and, in java.time.chrono: MinguoDate, HijrahDate, JapaneseDate, and ThaiBuddhistDate;

- The interface java.lang.ProcessHandle and its implementation classes;

- The implementation classes of the collection factories in java.util: List.of, List.copyOf, Set.of, Set.copyOf, Map.of, Map.copyOf, Map.ofEntries, and Map.entry.

由于后续对需要对 `Valhalla` 项目的支持,如果这些类用于对象锁,根据 JVM 的配置会有对应的错误和警告。

```java
public static void main(String[] args) {
Integer i = Integer.valueOf(1);
synchronized (i) {
System.out.println("sync use value based class");
}
}
```

```log
-XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=1
# A fatal error has been detected by the Java Runtime Environment:
#
# Internal Error (synchronizer.cpp:485), pid=42306, tid=8707
# fatal error: Synchronizing on object 0x00000007ffe5f3b8 of klass java.lang.Integer at review.Main.main(Main.java:7)
```

```log
-XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=2
[0.068s][info][valuebasedclasses] Synchronizing on object 0x00000007ffe5f3b8 of klass java.lang.Integer
[0.068s][info][valuebasedclasses] at review.Main.main(Main.java:7)
[0.068s][info][valuebasedclasses] - locked <0x00000007ffe5f3b8> (a java.lang.Integer)
```

方法 `ObjectSynchronizer::handle_sync_on_value_based_class` 负责处理 `Value-Based Classes`


#### 轻量级锁实现

`LM_LIGHTWEIGHT` 是基于栈实现的,底层是一个数组。

```cpp
class JavaThread: public Thread {
LockStack _lock_stack;
}
class LockStack {
static const int CAPACITY = 8;
uint32_t _top;
const uintptr_t _bad_oop_sentinel = badOopVal;
oop _base[CAPACITY]
}
```
如果对象锁的状态是未锁定,则使用 CAS 获取锁。
```cpp
inline bool LightweightSynchronizer::fast_lock_try_enter(oop obj, LockStack& lock_stack, JavaThread* current) {
markWord mark = obj->mark();
while (mark.is_unlocked()) {
ensure_lock_stack_space(current);
// Try to swing into 'fast-locked' state.
markWord locked_mark = mark.set_fast_locked();
markWord old_mark = mark;
mark = obj->cas_set_mark(locked_mark, old_mark);
if (old_mark == mark) {
lock_stack.push(obj);
return true;
} }
return false;
}
```

获取轻量级锁的时候需要确保 `lock_stack` 还有容量,有两种措施:

1.`lock_stack` 中已经被升级为重量级锁的对象移除。假如线程 A 持有锁对象的轻量级锁,如果此时线程 B 获取对象锁时,对象锁就会升级为重量级锁。

2.`lock_stack` 中最早的锁升级为重量级锁。

升级之后的对象会重 `lock_stack` 移除。

```cpp
void LightweightSynchronizer::ensure_lock_stack_space(JavaThread* current) {
LockStack& lock_stack = current->lock_stack();
// Make room on lock_stack
if (lock_stack.is_full()) {
// Inflate contended objects
LockStackInflateContendedLocks().inflate(current);
if (lock_stack.is_full()) {
// Inflate the oldest object
inflate_fast_locked_object(lock_stack.bottom(), ObjectSynchronizer::inflate_cause_vm_internal, current, current);
}
}
}
```
#### 锁自旋
当线程获取锁失败时,就会进入自旋状态,循环退出有几个条件:
1. 对象锁升级为重量级锁。
2. 线程需要进入安全点。
3. 达到自旋最大次数。
4. 获取到轻量级锁,只有获取到轻量级锁返回的是 true。
```cpp
bool LightweightSynchronizer::fast_lock_spin_enter(oop obj, LockStack& lock_stack, JavaThread* current, bool observed_deflation) {
// LightweightFastLockingSpins = 13
const int log_spin_limit = os::is_MP() ? LightweightFastLockingSpins : 1;
const int log_min_safepoint_check_interval = 10;
markWord mark = obj->mark();
const auto should_spin = [&]() {
if (!mark.has_monitor()) {
return true;
} else if (observed_deflation) {/*observed_deflation is false*/ }
return false;
};
// Always attempt to lock once even when safepoint synchronizing.
bool should_process = false;
for (int i = 0; should_spin() && !should_process && i < log_spin_limit; i++) {
// Spin with exponential backoff.
const int total_spin_count = 1 << i;
const int inner_spin_count = MIN2(1 << log_min_safepoint_check_interval, total_spin_count);
const int outer_spin_count = total_spin_count / inner_spin_count;
for (int outer = 0; outer < outer_spin_count; outer++) {
should_process = SafepointMechanism::should_process(current);
if (should_process) { break; }
for (int inner = 1; inner < inner_spin_count; inner++) { SpinPause(); }
}
if (fast_lock_try_enter(obj, lock_stack, current)) return true;
}
return false;
}
```

`LightweightFastLockingSpins`默认为 13,各个循环变量的次数如下,只有当 i > 10 时,`outer_spin_count` 代码的循环次数才会增加。

| Iteration (i) | total_spin_count | inner_spin_count | outer_spin_count |
|---------------|------------------|------------------|------------------|
| 0 | 1 | 1 | 1 |
| 1 | 2 | 2 | 1 |
| 2 | 4 | 4 | 1 |
| 3 | 8 | 8 | 1 |
| 4 | 16 | 16 | 1 |
| 5 | 32 | 32 | 1 |
| 6 | 64 | 64 | 1 |
| 7 | 128 | 128 | 1 |
| 8 | 256 | 256 | 1 |
| 9 | 512 | 512 | 1 |
| 10 | 1024 | 1024 | 1 |
| 11 | 2048 | 1024 | 2 |
| 12 | 4096 | 1024 | 4 |

#### 重入锁

当栈顶元素与要获取的对象相同时,则加锁成功。

```cpp
inline bool LockStack::try_recursive_enter(oop o) {

int end = to_index(_top);
if (end == 0 || _base[end - 1] != o) {
return false;
}
_base[end] = o;
_top += oopSize;
return true;
}
```
## 重量级锁
//todo
当对象锁存在竞争时,轻量级就会升级为重量级锁,本小节说的重量级锁是基于 hash table 实现的新版本。
//TODO
实现较为复杂,某些地方需要邮件询问 OpenJDK 的开发者,后续再补充。
## 总结
//todo
本文介绍了 markword 的机构,对比了 Compact Object Headers 前后 synchronized 的实现原理。本文有很多地方没有说清楚,笔者需要邮件询问 OpenJDK 的开发者,后续再补充。
Binary file added assets/synchronized/new-lock-process.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit b735be2

Please sign in to comment.