揭秘Java世界中safepoint机制之线程进入safepoint机制解析四


线程进入Safepoint机制概述

并不是所有的 Java 线程都在执行 JIT 编译后的机器码。JVM 将执行状态分为以下四大类,它们进入 Safepoint 的机制各不相同:

线程当前执行状态进入 Safepoint 的机制原理
Compiled (JIT 编译码)执行到由 C1/C2 插入的 Polling 指令,触发 SIGSEGV 信号中断进入。
Interpreted (解释执行)字节码解释器(Template Interpreter)拥有一张派发表(Dispatch Table)。当触发 Safepoint 时,VM Thread 会把正常的派发表替换为 Safepoint 专用派发表。这样解释器在执行下一条字节码指令时,会自动路由到安全点处理代码。
Native (JNI 运行中)执行 Native 代码的线程不需要立即挂起,因为 Native 代码不会直接修改 Java 堆对象。VM Thread 会直接将其标记为“已处于安全点”。但当该 Native 线程执行完毕,准备**返回 Java 世界(Transition Back)**时,会在边界处检查 Safepoint 状态,若仍处于 STW,则在该边界处被挂起。
Blocked (阻塞/睡眠状态)线程处于 Object.wait()Thread.sleep() 或等待锁(Mux/Lock)状态。这类线程同 Native 一样,被 VM 视为默认安全。它们在被唤醒或者获取到锁准备恢复运行的瞬间,也会进行边界检查并阻塞。

处于Blocked(阻塞/睡眠状态)线程进入safepoint机制解析

在 OpenJDK 8 中,当 JVM 发起 Safepoint(全局安全点)请求时,正在执行 Java 字节码的线程需要通过主动轮询(Polling)来响应。然而,对于处于 Blocked(阻塞/睡眠,JVM 内部对应 _thread_blocked 状态) 的线程,其进入 Safepoint 的机制采用了“被动确认”“主动拦截”相结合的不对称设计。

简而言之:VM Thread 在发起 Safepoint 时,直接单方面认为 _thread_blocked 状态的线程已经处于 Safepoint;而该线程一旦试图唤醒并退出阻塞状态,则必须在边界处进行拦截,由其自身完成逻辑上的“进入”与等待。


1. 核心机制:非对称的安全状态确认

当线程处于 _thread_blocked 状态时(例如:因为 Object.wait()Thread.sleep()、等待进入 synchronized 块或等待本地 IO 阻塞),它绝对不会修改 Java 堆内存,也不会执行任何可能改变 Java 线程上下文的字节码。

因此,HotSpot 认为该线程是天然安全的

整个过程分为两个视角:

  1. VM Thread 视角:直接将该线程标记为 safe,无需等待其响应,直接推进 Safepoint 流程。
  2. Blocked 线程视角:在阻塞结束、准备恢复执行前,必须强制检查全局 Safepoint 状态。如果全局 Safepoint 正在进行,该线程将自我阻塞,直到 Safepoint 结束。

2. VM Thread 视角的源码分析:安全状态判定

当 VM Thread 执行 SafepointSynchronize::begin() 试图让所有线程进入 Safepoint 时,它会遍历所有的 JavaThread,并通过 safepoint_safe 函数评估线程当前的状态。

源码路径:share/vm/runtime/safepoint.cpp

// 检查特定线程在当前状态下是否对 Safepoint 安全
inline bool SafepointSynchronize::safepoint_safe(JavaThread *thread, JavaThreadState state) {
  switch(state) {
    case _thread_in_native:
      // JNI 线程同样被视为安全
      return true;

    case _thread_blocked:
      // 【关键点】如果线程处于 _thread_blocked 状态,VM Thread 直接返回 true!
      // 这意味着 VM Thread 不需要向该线程发送任何信号,也不需要等待它到达轮询点。
      // 在 VM Thread 眼里,这个线程此时此刻已经处于 Safepoint 中。
      return true;

    default:
      return false;
  }
}

SafepointSynchronize::begin() 的主循环中,正是通过这个判定来减少等待计数的:

void SafepointSynchronize::begin() {
  // ... 省略前置初始化代码 ...
  
  // 悬挂全局 Safepoint 状态,通知全系统
  _state = _synchronizing;
  OrderAccess::fence(); // 确保可见性

  // 循环等待所有非安全线程到达 Safepoint
  int iterations = 0;
  while (!is_all_java_threads_frozen()) {
    int still_running = 0;
    
    for (JavaThread *p = Threads::first(); p != NULL; p = p->next()) {
      JavaThreadState state = p->thread_state();
      
      // 检查线程是否安全
      if (safepoint_safe(p, state)) {
        // 如果是 _thread_blocked,直接视作已进入 Safepoint,不计入 still_running
        continue; 
      }
      
      // 如果不是安全状态(如 _thread_in_Java),则需要等待,计数加 1
      still_running++;
    }
    
    if (still_running == 0) {
      break; // 所有线程都安全了,成功达成 Safepoint
    }
    
    // 如果还有线程在运行,VM Thread 适当挂起等待
    os::naked_yield();
  }
  
  // 进入同步完成状态
  _state = _synchronized;
}


3. Blocked 线程视角的源码分析:退出时的状态拦截

既然 VM Thread 直接放行了 Blocked 线程,那么如果该线程在 Safepoint 期间突然被唤醒(例如 sleep 时间到了,或者持锁线程释放了锁),它如果直接继续执行,就会破坏 Safepoint 的独占性(如引发 GC 时的对象移动问题)。

因此,HotSpot 在所有状态切换的边界处设计了状态转换宏(State Transitions)。当 Blocked 线程试图切回运行状态(如 _thread_in_vm_thread_in_Java)时,会被强制拦截。

源码路径:share/vm/runtime/interfaceSupport.hpp

以典型的 ThreadBlockInVM 动作为例,它利用 C++ 的 RAII 机制,在析构函数(即退出 Blocked 状态时)强制检查 Safepoint:

class ThreadBlockInVM : public ThreadStateTransition {
 public:
  ThreadBlockInVM(JavaThread *thread) : ThreadStateTransition(thread) {
    // 进入构造函数:从 _thread_in_vm 切换为 _thread_blocked 状态
    trans_and_fence(_thread_in_vm, _thread_blocked);
  }
  
  ~ThreadBlockInVM() {
    // 【关键点】析构函数:当阻塞结束,线程试图离开 _thread_blocked 状态时
    // 强制调用转换函数,检查是否需要因 Safepoint 而拦截自己
    trans_from_java_blocked(_thread_in_vm);
  }
};

我们深入看 trans_from_java_blocked 的实现,它展示了拦截的底层逻辑:

void trans_from_java_blocked(JavaThreadState to) {
  // 1. 尝试将状态修改为目标状态(如 _thread_in_vm)
  _thread->set_thread_state(to);
  
  // 2. 内存屏障,确保状态立即对 VM Thread 可见
  OrderAccess::fence();
  
  // 3. 【核心拦截检查】检查当前 JVM 是否正在进行 Safepoint 同步
  // do_call_back() 会检查全局的 Safepoint 状态是否为 _synchronizing 或 _synchronized
  if (SafepointSynchronize::do_call_back()) {
     // 如果有 Safepoint 正在发生,绝不允许放行!
     // 必须调用 block() 将自己真正挂起,退回到 Safepoint 等待队列中
     SafepointSynchronize::block(_thread);
  }
}

源码路径:share/vm/runtime/safepoint.cpp 中的 block()

当上述拦截命中后,线程会进入 SafepointSynchronize::block 函数。在这里,它会把自己的状态再次认领为 _thread_blocked,然后死锁在 Safepoint_lock 上。

void SafepointSynchronize::block(JavaThread *thread) {
  
  // 获取当前线程保存的原本目标状态
  JavaThreadState state = thread->thread_state();
  
  // 【重新声明安全】为了防止在拿锁过程中全局 Safepoint 仍在推进,
  // 重新将自己的状态强制设置为 _thread_blocked,告诉 VM Thread:我很安全,不用管我。
  thread->set_thread_state(_thread_blocked);

  // 竞争互斥锁 Safepoint_lock
  MutexLocker mu(Safepoint_lock);
  
  // 循环检查:只要全局 Safepoint 没有结束,就在这里长眠
  while (SafepointSynchronize::is_synchronizing() || SafepointSynchronize::is_synchronized()) {
     // 挂起当前线程,等待 VM Thread 在 Safepoint 结束后执行 SafepointSynchronize::end() 时唤醒
     Safepoint_lock->wait(Mutex::_no_safepoint_check_flag);
  }

  // Safepoint 结束,VM Thread 已经唤醒了我们
  // 恢复线程原本想要切换去的那个目标状态(例如 _thread_in_vm),继续执行业务
  thread->set_thread_state(state);
}


4. 核心流程总结

配合 OpenJDK 8 的源码架构,一个 Blocked 线程在 Safepoint 期间的生命周期遵循以下轨迹:

[线程因某些原因阻塞] 
       │
       ▼
线程状态变为 _thread_blocked 
       │
       ▼
【VM Thread 发起 Safepoint】 ───> 扫描到该线程 ───> 命中 `safepoint_safe` ───> 视为“已在安全点” (直接略过)
       │
       ▼
【Blocked 线程被客观唤醒】
       │
       ▼
执行状态转换宏 (如 ~ThreadBlockInVM)
       │
       ▼
检测到 `SafepointSynchronize::do_call_back() == true`
       │
       ▼
进入 `SafepointSynchronize::block()`
       │
       ▼
状态保持 `_thread_blocked` ───> 阻塞在 `Safepoint_lock` ───> [真正的物理等待]
                                                               │
                                                               ▼
                                                  【VM Thread 完成工作,撤销 Safepoint】
                                                               │
                                                               ▼
                                                   线程被唤醒 ───> 恢复原状态 ───> 继续执行

在这里插入图片描述

通过这种免视等待(Optimistic Pass)出口拦截(Pessimistic Check)的非对称设计,HotSpot 完美避免了 VM Thread 去逐一唤醒或等待底层系统底层阻塞线程的性能开销,从而保证了进入 Safepoint 的整体高效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值