安全点与安全区域是JVM为实现可控、低延迟STW而设计的协作机制:安全点负责活跃线程在预设位置主动停靠并检查GC标志,安全区域则覆盖阻塞或JNI线程,允许其标记“已安全”以跳过等待,两者协同确保所有线程毫秒级达成全局一致状态。

Java 安全点和安全区域不是两个独立开关,而是 JVM 为实现可控、低延迟 STW(Stop-The-World)而设计的一套协作机制:一个管“正在跑”的线程,一个兜“没法动”的线程,共同确保所有用户线程在毫秒级内达成全局一致状态,让 GC 能安全开始。
安全点:活跃线程的主动停靠站
当 JVM 决定触发 GC(比如调用 System.gc() 或年轻代空间不足),它不会强制中断线程,而是设置一个全局标志(如 needs_gc)。所有正在执行 Java 字节码的线程,在到达预设的安全点位置时,会自动插入轻量轮询指令检查该标志。一旦发现需暂停,就立即保存寄存器和栈帧(含 OopMap),进入阻塞等待。
- 安全点位置有严格选择标准:方法返回前、循环跳转处、异常抛出点、方法调用后——这些位置天然具备状态稳定、执行窗口长、引用关系明确的特点
- 纯计算型长循环(如 while (i )可能长时间不遇安全点,JVM 会在 JIT 编译时自动插入额外轮询(启用 -XX:+UseCountedLoopSafepoints 可强化此行为)
- 线程不会在任意指令处被截断,避免对象字段只写一半、栈帧不完整等状态不一致风险
安全区域:阻塞/本地线程的免责区
有些线程根本不在 JVM 控制下运行,比如正执行 Thread.sleep()、Object.wait()、LockSupport.park(),或陷入 JNI 本地方法调用、系统 I/O 阻塞(如 Socket.read())。它们无法主动轮询标志,也不可能“走到”下一个安全点。
- 这类线程一旦进入已知安全的状态(即引用关系不会再变),就会标记自己为 in_safe_region = true
- JVM 发起 GC 时,直接跳过这些线程,无需等待其唤醒或返回 —— 这是避免 STW 无限延迟的关键兜底
- 当线程准备退出安全区域(例如 sleep 结束、native 方法返回),必须先检查 GC 是否已完成;若未完成,则在下一个安全点前阻塞,确保堆状态不被破坏
两者协同:覆盖所有线程状态盲区
GC 触发后,JVM 实际等待的是:所有线程要么停在某个安全点,要么声明自己已在安全区域内。只有全部满足,才正式开始根节点枚举、标记等操作。
- 没有安全点 → GC Roots 枚举不可靠(OopMap 无处落脚),可能漏掉存活对象
- 没有安全区域 → GC 可能卡在等待一个阻塞在 read() 中的线程上,STW 时间从毫秒级飙升至秒级甚至更久
- 实际停顿时间 ≈ 最慢 Java 线程到达安全点的耗时 + 安全区域线程的唤醒与同步延迟,而非理论上的“同时暂停”
典型场景中的配合过程
假设一次 Full GC 被触发:
- 正在执行 for 循环的线程 A:循环末尾遇到安全点,轮询到 GC 标志,挂起
- 刚调用 System.currentTimeMillis() 并进入 native 的线程 B:进入 JNI 后立即标记为 in_safe_region,GC 不等它
- 处于 wait() 状态的线程 C:已在安全区域内,GC 直接开始;它被 notify 唤醒后,先检查 GC 状态,再继续执行
- 刚从 synchronized 块退出的线程 D:方法返回处是天然安全点,轮询后立即响应
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











