G1 GC - 大型后台 I/O 导致 JVM 无响应 - 暂停 8 秒

Dhi*_*osh 5 java garbage-collection g1gc pause

我们有一个几乎需要实时响应的 Java 应用程序。但我们也看到了长达 8 秒的停顿。

特殊运行条件:

  1. 在某些时间间隔,应用程序会将大小高达 1.5G 的巨大数据快照序列化到磁盘 (SSD) 中。
  2. m/c 中的其他一些共存进程偶尔会发生一些繁重的后台 I/O。

我们正在观察的是,这个大型序列化写入发生的间隔期间,如果 GC 偶然启动,它会导致 6 到 8 秒的巨大暂停,JVM 处于完全无响应/冻结状态。

从 JFR 记录可以看出,

  1. 除了写入磁盘的线程之外,所有线程都处于停放/等待/睡眠状态。
  2. G1 GC 大约需要 200 毫秒才能完成 GC。

来自 GC 日志:2018-09-05T18:23:40.277+0000: 39892.345:应用程序线程停止的总时间:8.3785112秒,停止线程花费:8.3765855 秒

Java 版本:java 版本“1.8.0_181”Java(TM) SE 运行时环境(构建 1.8.0_181-b25)Java HotSpot(TM) 64 位服务器 VM(构建 25.181-b25,混合模式)

问题:

  1. 当 GC 和后台 I/O 同时发生时,为什么非 GC JVM 会暂停 8 秒?
  2. 如何克服这个大停顿?

JFR 文件:https : //www.filehosting.org/file/details/756217/Run.jfr

Ale*_*zin 3

你的问题不在于 GC,而在于 JVM Stop-The-World 机制 - safepoints。JVM 等待所有线程停放在安全点上,然后再开始 GC 相关工作。

如果您的 Java 代码使用内存映射文件,您可能需要将其替换为常规 IO。内存映射(尤其是大量写入)对安全点机制影响很大。访问内存映射文件的线程可能会被等待写入/读取内存页面的操作系统阻塞。如果此时触发 GC,则必须等待 IO 阻塞线程恢复并到达下一个安全点检查。

更新:说起来很简单,如果 Java 线程在RandomFile/ Channelread 方法上被阻塞,它不会阻止 JVM 安全点。但是,如果 Java 线程在对内存映射文件进行读/写操作时被阻塞,那么在线程解除阻塞之前,JVM 无法进入安全点。如果此类访问包含在循环中,它甚至可能会等到循环在某些条件下完成。

导致长时间“停止线程”的另一个问题可能是循环。如果您有带有 int 计数器的简单循环,JVM 会认为它“快”并且可能会忽略循环体内的安全点检查。