我们使用 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 有关系,但找不到任何相关信息。我在哪里可以阅读有关此主题的更多信息?
Activity 的最终目标:了解在这种情况下发生了什么,影响这一点的女巫因素以及我们如何减少此类操作的锁定时间。
评论中的一些细节:
最佳答案
Java Flight Recorder 的一大缺点是它只显示 Java 堆栈,完全忽略了 native 和 VM 部分。
async-profiler在这个意义上更准确。如果您在 lock
中运行它在启用 native 堆栈的分析模式下,它将显示获取这些锁的 JVM 中的确切位置。示例命令:
./profiler.sh -d 60 -e lock --cstack fp -f profile.html -o flamegraph=total PID
-d 60
运行分析 60 秒 -e lock
配置文件锁争用 --cstack fp
记录 C( native )堆栈 -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++ 代码中。ReferenceHandler
线程在 GC 开始之前离开临界区。在持有此锁的同时,JVM 可以安全地将新发现的弱引用附加到挂起列表。综上所述,多个Java线程同时尝试从Heap中分配一个对象,但Heap已满。因此,垃圾回收开始,分配线程在
Reference$Lock
上被阻塞。 - 引用挂起列表锁。关于
Reference$Lock
的争用本身不是问题。分配线程无论如何都不能继续,直到 GC 回收足够的内存。实际问题是并发垃圾回收跟不上分配率 .要缓解该问题,请尝试以下一种或多种方法:
ConcGCThreads
; InitiatingHeapOccupancyPercent
提前启动并发 GC 周期; 增加堆可能是最有效的。
顺便说一句,async-profiler 还有其他有用的模式来诊断 GC 相关问题:
-e cpu
显示占用最多 CPU 时间的内容。 Java 和 VM 线程一起显示在同一图表上,因此您可以了解与应用程序工作相比,GC Activity 是否太高。 -e alloc
显示分配最多的代码。在研究如何降低分配率时,它特别有用。 关于java - JVM 中对 java.lang.ref.Reference$Lock 上的数组分配的大量锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65932525/