JVM常用概念之安全点

1.什么是安全点?

  • 安全点是执行线程状态被充分描述的执行范围。安全点是常见的 JVM 实现细节;
  • 在安全点处,mutator线程处于与堆交互的已知且定义明确的点。这意味着堆栈上的所有引用都已映射(在已知位置),并且 JVM 可以对所有引用进行解释。只要线程保持在安全点处,我们就可以安全地操作堆 + 堆栈,这样当线程离开安全点时,它对世界的视图就保持一致。
  • 目前所有的 JVM 都对全局安全点有一定的要求
  • 如果Java 线程被锁或同步块阻塞、在监视器上等待、处于暂停状态或被阻塞 IO 阻塞,则该线程处于安全点。本质上,这些都符合 Java 线程有序取消调度的条件,并且是线程被置于安全点之前的整理工作的一部分。
  • Java 线程在执行 JNI 代码时处于安全点。在跨越本机调用边界之前,堆栈在移交给本机代码之前处于一致状态。这意味着线程在处于安全点时仍可以运行。
  • 正在执行字节码的 Java 线程不在安全点(或者至少 JVM 不能假定它处于安全点)。
  • 如果 Java 线程在非安全点处被操作系统中断,则在取消调度之前不会将其带到安全点。
  • JVM 和正在运行的 Java 线程在安全点方面具有以下关系:
    • JVM 无法强制任何线程进入安全点状态。
    • JVM 可以阻止线程离开安全点状态。

那么 JVM 如何让所有线程进入安全点状态?问题在于将线程暂停在已知状态,而不仅仅是中断它。为了实现此目标,如果观察到“安全点标志”,JVM 会让 Java 线程在方便的位置自行暂停。

2.将 Java 线程置于安全点

Java 线程以“合理”间隔轮询“安全点标志”(全局或线程级别),并在观察到“转到安全点”标志时转换为安全点状态(线程在安全点被阻止)。这很简单,但由于我们不想花费所有时间来检查是否需要停止 C1/C2 编译器(-client/-server JIT 编译器),因此尝试将安全点轮询保持在最低限度。除了标志检查本身的成本之外,维护“已知状态”还会大大增加某些优化的实施复杂性,因此将安全点保持得更远可以为优化开辟更广泛的空间。这些考虑因素结合起来导致安全点轮询的以下位置:

  • 在解释器中运行时,任意 2 个字节码之间(有效)
  • 在 C1/C2 编译代码中的“非计数”循环回溯边缘上
  • C1/C2 编译代码中的方法入口/出口(Zing 的入口,OpenJDK 的出口)。请注意,当方法被内联时,编译器将删除这些安全点轮询。

如果您是那种为了有趣(或为了利益,或两者兼而有之)而研究汇编的人,您可以通过查找以下内容在 -XX:+PrintAssembly 输出中找到安全点轮询:

OpenJDK 上的“{poll}”或“ {poll return}”

更具体的解释是:

  1. 线程可以处于安全点,也可以不处于安全点。处于安全点时,线程的 Java 机器状态表示描述得很好,并且可以被 JVM 中的其他线程安全地操作和观察。不处于安全点时,线程的 Java 机器状态表示将不会被 JVM 中的其他线程操作。[请注意,其他线程不会操作线程的实际逻辑机器状态,只是操作该状态的表示。更改机器状态表示的一个简单示例是更改 Java 引用堆栈变量由于重新定位该对象而指向的虚拟地址。引用变量的逻辑状态不受此更改的影响,因为引用仍然引用同一个对象,并且即使两个引用变量暂时指向不同的虚拟地址,它们在逻辑上仍然相等]。

  2. “处于安全点”并不意味着“被阻塞”(例如 JNI 代码在安全点运行),但“被阻塞”总是在安全点发生。

  3. JVM 可以选择达到全局安全点(又称为 Stop-The-World),其中所有线程都处于安全点,并且不能离开安全点,除非 JVM 决定允许它们离开。这对于执行需要所有线程处于良好描述状态的各种工作(如某些 GC 操作、类加载期间的去优化等)非常有用。

  4. 一些 JVM 可以将单个线程带到安全点,而无需全局安全点。例如,Zing 使用术语 Checkpoint(首次发表于 [1])来描述一种 JVM 机制,该机制将线程单独传递到线程特定的安全点,以在单个线程状态下执行某些非常短的操作,而无需 Stop-The-Wolrd 暂停。

  5. 当你编写不安全的 Java 代码时,你必须假设安全点可能出现在任意两个字节码之间。

  6. 不安全调用不需要在其中包含安全点(而且许多/大多数都没有),但它们可以包含一个或多个安全点。例如,不安全的 memoryCopy 可以包含定期的安全点机会(例如每 16KB 获取一个安全点)。Zing 确实如此,因为我们做了很多幕后工作来控制 TTSP。

  7. 所有 [实际] JVM 都应用了一些高效的机制来频繁跨越安全点机会,其中线程实际上不会进入安全点,除非其他人指示需要这样做。例如,生成的代码中的大多数调用站点和循环后沿将包含某种安全点轮询序列,相当于“我现在需要转到安全点吗?”。许多 HotSpot 变体(OpenJDK 和 Oracle JDK)目前使用一个简单的全局“转到安全点”指示器,其形式是页面,当需要安全点时,该页面受到保护,否则不受保护。此机制的安全点轮询相当于从该页面中的固定地址加载。如果加载使用 SEGV 捕获,则线程知道它需要进入安全点。Zing 使用不同的、每个线程的转到安全点指示器,其效率相似。

  8. 所有 JNI 代码都在安全点执行。处于安全点时,JNI 代码无法更改或观察执行线程的 Java 机器状态。JNI 代码对 Java 状态的任何操作或观察都是通过 JNI API 调用实现的,这些调用在 API 调用期间离开安全点,然后再次进入安全点,然后返回调用 JNI 代码。这种“跨越 JNI 线”是大多数 JNI 调用和 API 开销所在,但它相当快(进入和离开安全点通常相当于一些 CAS 操作)。

3.可能遇到安全点的操作

  • 去优化
  • 打印线程
  • 打印JNI
  • 查找死锁
  • 线程转储
  • 启用偏向锁定
  • 撤销偏见
  • 堆转储器
  • 获取所有堆栈跟踪信息
  • 11
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值