赞
踩
那么 JVM 如何让所有线程进入安全点状态?问题在于将线程暂停在已知状态,而不仅仅是中断它。为了实现此目标,如果观察到“安全点标志”,JVM 会让 Java 线程在方便的位置自行暂停。
Java 线程以“合理”间隔轮询“安全点标志”(全局或线程级别),并在观察到“转到安全点”标志时转换为安全点状态(线程在安全点被阻止)。这很简单,但由于我们不想花费所有时间来检查是否需要停止 C1/C2 编译器(-client/-server JIT 编译器),因此尝试将安全点轮询保持在最低限度。除了标志检查本身的成本之外,维护“已知状态”还会大大增加某些优化的实施复杂性,因此将安全点保持得更远可以为优化开辟更广泛的空间。这些考虑因素结合起来导致安全点轮询的以下位置:
如果您是那种为了有趣(或为了利益,或两者兼而有之)而研究汇编的人,您可以通过查找以下内容在 -XX:+PrintAssembly 输出中找到安全点轮询:
OpenJDK 上的“{poll}”或“ {poll return}”
更具体的解释是:
线程可以处于安全点,也可以不处于安全点。处于安全点时,线程的 Java 机器状态表示描述得很好,并且可以被 JVM 中的其他线程安全地操作和观察。不处于安全点时,线程的 Java 机器状态表示将不会被 JVM 中的其他线程操作。[请注意,其他线程不会操作线程的实际逻辑机器状态,只是操作该状态的表示。更改机器状态表示的一个简单示例是更改 Java 引用堆栈变量由于重新定位该对象而指向的虚拟地址。引用变量的逻辑状态不受此更改的影响,因为引用仍然引用同一个对象,并且即使两个引用变量暂时指向不同的虚拟地址,它们在逻辑上仍然相等]。
“处于安全点”并不意味着“被阻塞”(例如 JNI 代码在安全点运行),但“被阻塞”总是在安全点发生。
JVM 可以选择达到全局安全点(又称为 Stop-The-World),其中所有线程都处于安全点,并且不能离开安全点,除非 JVM 决定允许它们离开。这对于执行需要所有线程处于良好描述状态的各种工作(如某些 GC 操作、类加载期间的去优化等)非常有用。
一些 JVM 可以将单个线程带到安全点,而无需全局安全点。例如,Zing 使用术语 Checkpoint(首次发表于 [1])来描述一种 JVM 机制,该机制将线程单独传递到线程特定的安全点,以在单个线程状态下执行某些非常短的操作,而无需 Stop-The-Wolrd 暂停。
当你编写不安全的 Java 代码时,你必须假设安全点可能出现在任意两个字节码之间。
不安全调用不需要在其中包含安全点(而且许多/大多数都没有),但它们可以包含一个或多个安全点。例如,不安全的 memoryCopy 可以包含定期的安全点机会(例如每 16KB 获取一个安全点)。Zing 确实如此,因为我们做了很多幕后工作来控制 TTSP。
所有 [实际] JVM 都应用了一些高效的机制来频繁跨越安全点机会,其中线程实际上不会进入安全点,除非其他人指示需要这样做。例如,生成的代码中的大多数调用站点和循环后沿将包含某种安全点轮询序列,相当于“我现在需要转到安全点吗?”。许多 HotSpot 变体(OpenJDK 和 Oracle JDK)目前使用一个简单的全局“转到安全点”指示器,其形式是页面,当需要安全点时,该页面受到保护,否则不受保护。此机制的安全点轮询相当于从该页面中的固定地址加载。如果加载使用 SEGV 捕获,则线程知道它需要进入安全点。Zing 使用不同的、每个线程的转到安全点指示器,其效率相似。
所有 JNI 代码都在安全点执行。处于安全点时,JNI 代码无法更改或观察执行线程的 Java 机器状态。JNI 代码对 Java 状态的任何操作或观察都是通过 JNI API 调用实现的,这些调用在 API 调用期间离开安全点,然后再次进入安全点,然后返回调用 JNI 代码。这种“跨越 JNI 线”是大多数 JNI 调用和 API 开销所在,但它相当快(进入和离开安全点通常相当于一些 CAS 操作)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。