使用 NIO 直接缓冲区时设置 -XX:+DisableExplicitGC 的影响

Impact of setting -XX:+DisableExplicitGC when NIO direct buffers are used

我们正在构建一个具有严格性能 SLA 的 Web 应用程序,由于 System.gc() 调用导致 JVM 外出午餐,这些 SLA 会定期被违反。我们已经进行了一些调试,并确定在所有情况下都是调用 System.gc() 的内部应用程序服务器代码。在应用服务器启动或部署应用程序时,这种情况会发生几次,我们并不关心。但是,System.gc() 也会在应用启动时定期触发,并且 运行 通过内部应用服务器调用 NIO 类。这是我们能够捕获此事件的堆栈跟踪:

3XMTHREADINFO      "WebContainer : 25" J9VMThread:0x0000000006FC5D00, j9thread_t:0x00007F60E41753E0, java/lang/Thread:0x000000060B735590, state:R, prio=5
3XMJAVALTHREAD            (java/lang/Thread getId:0xFE, isDaemon:true)
3XMTHREADINFO1            (native thread ID:0x1039, native priority:0x5, native policy:UNKNOWN)
3XMTHREADINFO2            (native stack address range from:0x00007F6067621000, to:0x00007F6067662000, size:0x41000)
3XMCPUTIME               CPU usage total: 80.222215853 secs
3XMHEAPALLOC             Heap bytes allocated since last GC cycle=1594568 (0x1854C8)
3XMTHREADINFO3           Java callstack:
4XESTACKTRACE                at java/lang/System.gc(System.java:329)
4XESTACKTRACE                at java/nio/Bits.syncReserveMemory(Bits.java:721)
5XESTACKTRACE                   (entered lock: java/nio/Bits@0x000000060000B690, entry count: 1)
4XESTACKTRACE                at java/nio/Bits.reserveMemory(Bits.java:766(Compiled Code))
4XESTACKTRACE                at java/nio/DirectByteBuffer.<init>(DirectByteBuffer.java:123(Compiled Code))
4XESTACKTRACE                at java/nio/ByteBuffer.allocateDirect(ByteBuffer.java:306(Compiled Code))
4XESTACKTRACE                at com/ibm/ws/buffermgmt/impl/WsByteBufferPoolManagerImpl.allocateBufferDirect(WsByteBufferPoolManagerImpl.java:706(Compiled Code))
4XESTACKTRACE                at com/ibm/ws/buffermgmt/impl/WsByteBufferPoolManagerImpl.allocateCommon(WsByteBufferPoolManagerImpl.java:612(Compiled Code))
4XESTACKTRACE                at com/ibm/ws/buffermgmt/impl/WsByteBufferPoolManagerImpl.allocateDirect(WsByteBufferPoolManagerImpl.java:527(Compiled Code))
4XESTACKTRACE                at com/ibm/io/async/ResultHandler.runEventProcessingLoop(ResultHandler.java:507(Compiled Code))
4XESTACKTRACE                at com/ibm/io/async/ResultHandler.run(ResultHandler.java:905(Compiled Code))
4XESTACKTRACE                at com/ibm/ws/util/ThreadPool$Worker.run(ThreadPool.java:1864(Compiled Code))
3XMTHREADINFO3           Native callstack:
4XENATIVESTACK               (0x00007F61083DD122 [libj9prt26.so+0x13122])
4XENATIVESTACK               (0x00007F61083EA79F [libj9prt26.so+0x2079f])
....

是否有人知道如果我们通过启用 -XX:+DisableExplicitGC(或者实际上在我们的例子中通过设置 -Xdisableexplicitgc 来关闭对 ​​System.gc() 的调用会产生什么影响,因为我们 运行 IBM JRE 上的 Websphere,做同样的事情)?我们当然不想造成内存泄漏。我一直无法找到直接参考,说明为什么 NIO 中的 System.gc() 调用实际上是必需的,并且没有代码注释专门解决它在 JDK 代码中出现的地方, 或者: http://hg.openjdk.java.net/jdk8u/jdk8u-dev/jdk/file/4a1e42601d61/src/share/classes/java/nio/Bits.java

如果由于使用 NIO 而完全禁用 System.gc() 不是一个好主意,我们至少可以做些什么来降低调用它的频率吗?似乎我们可以设置 -XX:MaxDirectMemorySize,但这似乎只会设置分配内存量的上限,并且很可能会产生不利影响。

禁用显式 GC 不会阻止缓冲区,因此不会阻止收集它们所持有的本机内存。但是可能会延迟很长时间收款

这意味着直接缓冲区分配的内存在被回收之前可能会累积很长时间。从长远来看 运行 这并不是真正的泄漏,但它会增加峰值内存使用量。

http://hg.openjdk.java.net/jdk8u/jdk8u-dev/jdk/file/4a1e42601d61/src/share/classes/java/nio/Bits.java

据我了解,当达到 reserveMemory 限制时,会调用 System.gc() 来释放缓冲区。保留请求的数量后,ByteBuffer.allocateDirect 将调用 Unsafe.allocateMemory,如果它尝试 mmap 失败,它可能会执行自己的 GC 调用,这不应受到 DisableExplicitGC 的影响。

is there at least something we can do to reduce the frequency at which it's called?

仅在达到 MaxDirectMemorySize 限制时调用。如果您可以调整您的 GC 或应用程序代码,使其满足以下选项之一:

  • 它使用一组固定的缓冲区(-> 永远不会超过限制)
  • 尽早收集缓冲区(短暂的缓冲区 -> 在年轻的 GC 中死亡)
  • 在直接缓冲区 space 运行 出来之前定期收集老年代
  • 使用堆缓冲区而不是直接缓冲区

那么 System.gc() 就不需要打电话了。

在热点上还有一个 ExplicitGCInvokesConcurrent 选项。也许IBM的VM有类似的东西。