假设我需要垃圾收集器的帮助。 减少 GC 时间的方法之一是更快地到达安全点。
在启动 GC 之前,所有线程必须停放在安全点。
安全点是执行流程中的一个“特殊位置”,可以安全地停止线程以执行GC。
我想最大限度地减少代码中等待安全点的时间。 哪种编码风格可以帮助我更快地到达安全点?
什么风格会让这项任务更不舒服?
在风格下,我指的是语言中有助于(或阻止)快速“获取”安全点的一切。
示例:
磁盘 IO 工作是否会使问题变得更加严重?
JNI 是否会让问题变得更加严重?
Thread.sleep() 会让事情变得更困难吗?
synchronized(..) 会让事情变得更难吗?
等等..
简而言之,哪些java操作和方法会使快速“获取”安全点出现问题?有什么帮助?
最佳答案
让我们从以下注释开始(来自 OpenJDK 源代码中的“src/hotspot/share/runtime/safepoint.cpp”文件):
// Java threads can be in several different states and are
// stopped by different mechanisms:
//
// 1. Running interpreted
// The interpreter dispatch table is changed to force it to
// check for a safepoint condition between bytecodes.
// 2. Running in native code
// When returning from the native code, a Java thread must check
// the safepoint _state to see if we must block. If the
// VM thread sees a Java thread in native, it does
// not wait for this thread to block. The order of the memory
// writes and reads of both the safepoint state and the Java
// threads state is critical. In order to guarantee that the
// memory writes are serialized with respect to each other,
// the VM thread issues a memory barrier instruction
// (on MP systems). In order to avoid the overhead of issuing
// a memory barrier for each Java thread making native calls, each Java
// thread performs a write to a single memory page after changing
// the thread state. The VM thread performs a sequence of
// mprotect OS calls which forces all previous writes from all
// Java threads to be serialized. This is done in the
// os::serialize_thread_states() call. This has proven to be
// much more efficient than executing a membar instruction
// on every call to native code.
// 3. Running compiled Code
// Compiled code reads a global (Safepoint Polling) page that
// is set to fault if we are trying to get to a safepoint.
// 4. Blocked
// A thread which is blocked will not be allowed to return from the
// block condition until the safepoint operation is complete.
// 5. In VM or Transitioning between states
// If a Java thread is currently running in the VM or transitioning
// between states, the safepointing code will wait for the thread to
// block itself when it attempts transitions to a new state.
//
据此我得出以下结论:
- 当 JVM 线程解释字节码时,它可以到达任何字节码之后的安全点。
- 当 JVM 线程运行已 JIT 编译的代码时,其行为取决于 JIT 编译何时/何处注入(inject)指令以轮询“安全点轮询”页面。
- 当 native (JNI)线程运行时,从安全点的角度来看,它会被忽略。不过,我相信,如果安全点正在进行中,某些 JNI 调用将导致 native 线程阻塞。
- 如果 Java 线程被阻止(例如,被线程调度程序或 I/O 系统调用阻止)或状态发生更改,则安全点可以继续执行。
但这只能部分回答问题。问题在于 JIT 编译代码中的安全点轮询是通过 JIT 编译器添加的指令完成的。控制此操作的逻辑未指定1,并且可能依赖于实现。此外,我听说使用 System.arraycopy 和其他内置/内在操作进行内存复制可能会在没有安全点检查的情况下花费很长时间。
关于您的具体示例:
Is disk IO work making it more problematic?
显式 I/O(例如通过 read
和 write
调用)没有问题。内存映射文件 I/O 可能会出现问题;见下文。
Is JNI making it more problematic?
没有。
Is
Thread.sleep()
making it more hard?
没有。
Is
synchronized(..)
making it harder?
没有。
对于您的一般问题:
What style makes this task [of reaching safepoints] more uncomfortable?
避免长时间计数的循环。
避免会产生大量虚拟内存页面缺失的事情。
页面缺失问题是,当应用程序代码尝试读取或写入当前不在 RAM 中的虚拟内存页面时,操作系统必须从磁盘读取它。这种情况对应用程序来说是透明的……但是在这种情况发生时,尝试访问页面的线程被阻塞。在此过程中,它将无法到达下一个安全点。
容易导致页面未命中次数过多的因素有:
- 在 RAM 不足的计算机上运行具有大量堆或堆外内存使用量的 JVM,或者
- 使用大内存映射文件,特别是当它们大于可用 RAM 时。
如果磁盘设备速度较慢,这种影响将会加剧。
<小时/>1 - 根据 this source ,轮询被插入到非计数循环中的向后分支以及方法入口或导出处。这是依赖于平台的
关于java GC : what programming style makes safe point attainment easier (faster)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31217855/