java - JVM 中对 java.lang.ref.Reference$Lock 上的数组分配的大量锁定
问题描述
我们使用 Java Flight Recorder 分析我们的应用程序,并在 java.lang.ref.Reference$Lock 对象上发现了大量锁。
我调查了堆栈跟踪中的一些地方,并发现在所有情况下 - 都有数组分配
代码示例(图像上的位置 3):
public static char[] copyOfRange(char[] original, int from, int to) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
// stacktrace points on next line
char[] copy = new char[newLength];
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
我怀疑这种锁定与 GC 有关,但找不到任何相关信息。我可以在哪里阅读有关此主题的更多信息?
活动的最终目标:了解在这种情况下发生了什么,影响这种情况的因素以及我们如何减少此类操作的锁定时间。
评论中的一些细节:
- 爪哇 8
- 堆 512Mb
- 气相色谱-G1
- 通过实验我发现,锁定时间随着堆大小的增加而减少。
解决方案
Java Flight Recorder 的一大缺点是它只显示 Java 堆栈,完全忽略了本机和 VM 部分。
async-profiler在这个意义上要准确得多。如果您在lock
使用本机堆栈的分析模式下运行它,它将显示在 JVM 中获取这些锁的确切位置。示例命令:
./profiler.sh -d 60 -e lock --cstack fp -f profile.html -o flamegraph=total PID
-d 60
运行分析 60 秒-e lock
配置文件锁定争用--cstack fp
记录 C(本机)堆栈-f profile.html
输出文件名(async-profiler 2.0 中的 HTML 格式,或 1.x 中的 SVG)-o flamegraph=total
使用总锁定等待时间作为计数器构建火焰图PID
Java 进程 ID
在此示例中,火焰图突出显示了Reference$Lock
实例上的锁争用。堆栈跟踪的 Java 部分显示为绿色。这与您在 JFR 中看到的堆栈跟踪相匹配。就像您的情况一样,顶部的 Java 框架是Arrays.copyOfRange
(该图也显示了其他堆栈,但让我们关注第一个堆栈)。
黄色部分是原生 C++ 代码。让我解释一下那里发生了什么。
Arrays.copyOfRange
调用 VM 运行时函数OptoRuntime::new_array_nozero_C
。实际的数组分配发生在 JVM 的 C++ 代码中。JVM 无法从现有线程本地分配缓冲区 (TLAB) 分配数组,然后回退到新 TLAB 的慢速路径分配。
慢速路径分配也不成功,因为 Java Heap 中没有足够的空闲内存。因此,JVM 同步调用垃圾收集器。
在 GC 序言中,JVM 尝试获取保护未决引用列表的锁。这是为了确保
ReferenceHandler
线程在 GC 开始之前离开临界区。在持有这个锁的同时,JVM 可以安全地将新发现的弱引用附加到挂起列表。但是,该锁已经被另一个同时尝试以相同方式调用垃圾收集器的线程获取。当前线程被挂起,直到 GC 完成。
综上所述,多个 Java 线程同时尝试从 Heap 分配一个对象,但 Heap 已满。所以,垃圾收集开始了,分配线程被阻塞了Reference$Lock
——引用挂起列表锁。
争论Reference$Lock
本身不是问题。分配线程无论如何都无法继续,直到 GC 回收足够的内存。实际问题是并发垃圾收集跟不上分配率。
要缓解此问题,请尝试以下一种或多种方法:
- 增加堆大小;
- 降低分配率;
- 增加并发 GC 线程数 -
ConcGCThreads
; - 降低
InitiatingHeapOccupancyPercent
以更早地启动并发 GC 周期;
增加堆可能是最有效的。
顺便说一句,async-profiler 有其他有用的模式来诊断 GC 相关问题:
-e cpu
显示占用最多 CPU 时间的内容。Java 和 VM 线程一起显示在同一个图表上,因此您可以了解 GC 活动与应用程序工作相比是否太高。-e alloc
显示分配最多的代码。在调查如何降低分配率时它特别有用。
推荐阅读
- python - Condense List[Dict[key, list]] 以便重复键的列表转换为每个唯一键的列表列表
- vim - 在 CentOS 上将 vim 升级到版本 8:文件不包含节标题
- java - 从 CLI 构建的 Maven 失败但从 Eclipse 成功
- python - Python/Tkinter:是否可以通过单击外部来检测 OptionMenu/Menubutton 弹出窗口何时关闭?
- spring - 使用spring依赖管理无法解析依赖
- sockets - 在 FTP 服务器中,为什么服务器应该打开端口而不是客户端?
- go - 为什么我的 go 代码在使用 goroutine 和 channels 时会挂起?
- node.js - websocket 就绪状态 0(连接中)
- bash - 如何根据变量的长度来控制线的长度,并且能够使线均匀
- spring - Spring Boot 配置客户端刷新属性不起作用