线程响应gc信号靠协作式安全点检查而非强制中断;jvm通过polling page和轻量读指令实现主动挂起,在循环回跳等位置插入检查,线程自主暂停。

线程响应 GC 信号并进入等待状态,并不是靠“被强制打断”,而是依赖 JVM 预设的检查点和线程自身的协作式轮询。关键在于:线程只在安全点位置主动检查是否需要暂停,而不是在任意指令处被中断。
安全点不是中断点,而是协作检查点
HotSpot JVM 不采用抢占式中断(如发信号强行挂起线程),而是使用主动式中断机制。JVM 会设置一个全局标志位(通常通过内存保护页——polling page 实现),所有 Java 线程在执行到安全点位置时,都会插入一条轻量级的内存读取指令(如读 polling page)。一旦该页被操作系统标记为不可访问,读操作触发 segfault,JVM 的信号处理器捕获后,判断是否需进入 safepoint;若需,则线程挂起自身。
换句话说:线程不会“突然停下”,而是在循环末尾、方法返回前等位置,自然走到一个检查点,看到“该停了”,就自己停下来等。
循环中如何插入并响应安全点
对于长循环(尤其是计数器为 int 类型的循环),JIT 编译器会在每次循环回跳(loop back edge)处自动插入安全点检查。例如:
for (int i = 0; i
编译后,i++ 和条件判断之间会插入类似 test %rax, [polling_page] 的指令。只要循环体不包含阻塞或 native 调用,线程就会在每次迭代结束时检查一次。
但如果写成:for (long i = 0; i ,JIT 可能因认为循环次数“不可数”而省略安全点插入——这会导致 GC 等待时间剧增,是常见性能隐患。
方法调用为何天然适合作为安全点
方法调用前后是天然的安全点位置,原因有三:
- 栈帧结构稳定:调用前参数已压栈,调用后返回地址和局部变量布局明确,OopMap 可精准描述引用位置;
- JIT 编译器在生成 call 指令前后必然插入 safepoint poll;
- 即使方法内联优化发生,JIT 仍会在内联边界或控制流汇合点保留 poll 检查。
因此,频繁但轻量的方法调用(如 getter/setter、Stream 中间操作)反而有助于缩短 GC 停顿等待时间——线程更容易“及时抵达”安全点。
线程挂起的具体流程
当 VM 线程发起 GC 并调用 SafepointSynchronize::begin() 后:
- JVM 将 polling page 设为不可读(Linux 下 mprotect(PROT_NONE));
- 所有正在运行的 Java 线程,只要执行到下一个 safepoint poll,就会触发 SIGSEGV;
- 信号处理函数识别出这是 safepoint 请求,将线程状态设为 _thread_blocked 或 _thread_in_native_trans,并挂起线程;
- 一旦所有可挂起线程(排除 _thread_in_native 或 _thread_blocked 状态)都停稳,STW 阶段正式开始。
注意:处于 sleep、wait、park 或 native 方法中的线程不会立即响应。它们需依靠 Safe Region 机制——进入阻塞前声明“我已安全”,JVM 在同步阶段直接忽略它们。










