VMThread

VM Thread就是大家平时说的JVM线程,只有一个实例,也就是虚拟机创建过程中只会被创建一次C++层面,并且在虚拟机销毁的时候会被销毁。

具体的作用是开启一个无限循环队列(Whiletrue),然后不断地从一个VM_Operation队列中取出VM_Operation并且执行,如果没有VM_Operation就等待一会。

VM_Operation是通过其他线程放入到队列中的,所以类似 生产者消费者面试。VM_Operation有许多种,大概四类

#define VM_OP_ENUM(type)   VMOp_##type,
// Note: When new VM_XXX comes up, add 'XXX' to the template table.
#define VM_OPS_DO(template)                       
  template(Dummy)                                 
  template(ThreadStop)                            
  template(ThreadDump)                            
  template(PrintThreads)                          
  template(FindDeadlocks)                         
  template(ForceSafepoint)                        
  template(ForceAsyncSafepoint)                   
  template(Deoptimize)                            
  template(DeoptimizeFrame)                       
  template(DeoptimizeAll)                         
  template(ZombieAll)                             
  template(UnlinkSymbols)                         
  template(Verify)                                
  template(PrintJNI)                              
  template(HeapDumper)                            
  template(DeoptimizeTheWorld)                    
  template(CollectForMetadataAllocation)          
  template(GC_HeapInspection)                     
  template(GenCollectFull)                        
  template(GenCollectFullConcurrent)              
  template(GenCollectForAllocation)               
  template(ParallelGCFailedAllocation)            
  template(ParallelGCSystemGC)                    
  template(CGC_Operation)                         
  template(CMS_Initial_Mark)                      
  template(CMS_Final_Remark)                      
  template(G1CollectFull)                         
  template(G1CollectForAllocation)                
  template(G1IncCollectionPause)                  
  template(DestroyAllocationContext)              
  template(EnableBiasedLocking)                   
  template(RevokeBias)                            
  template(BulkRevokeBias)                        
  template(PopulateDumpSharedSpace)               
  template(JNIFunctionTableCopier)                
  template(RedefineClasses)                       
  template(GetOwnedMonitorInfo)                   
  template(GetObjectMonitorUsage)                 
  template(GetCurrentContendedMonitor)            
  template(GetStackTrace)                         
  template(GetMultipleStackTraces)                
  template(GetAllStackTraces)                     
  template(GetThreadListStackTraces)              
  template(GetFrameCount)                         
  template(GetFrameLocation)                      
  template(ChangeBreakpoints)                     
  template(GetOrSetLocal)                         
  template(GetCurrentLocation)                    
  template(EnterInterpOnlyMode)                   
  template(ChangeSingleStep)                      
  template(HeapWalkOperation)                     
  template(HeapIterateOperation)                  
  template(ReportJavaOutOfMemory)                 
  template(JFRCheckpoint)                         
  template(Exit)                                  
  template(LinuxDllLoad)                          
  template(RotateGCLog)                           
  template(WhiteBoxOperation)                     
  template(ClassLoaderStatsOperation)             

比较值得注意的是加载linux的动态链接库:

比较重要的就是GC操作

每个线程通过VMThread::execute把VM_Operation放入队列的之前会通过doit_prologue做检查,检查是否能把这个VM_Operation放入队列。

因为VM_Operation有可能会被多个java线程入队,但实际上只用一个入队就够了。比较典型的情况就是GC操作可能会被多个java线程入队,但实际只用GC一次就够了。实际执行VM_Operation是在VM_Operation::evaluate 中

具体过程伪代码:

  Java方法投递该VM_operation

  VMThread::execute (VM_Operation op)

  不是 VM_Thread来执行投递方法:

    if (op::doit_prologue()) {

     enqueue(op);

      op::doit_epilogue();

    }

  如果是VM_Thread的话,可以直接执行evaluate方法

void VMThread::execute(VM_Operation* op) {
  Thread* t = Thread::current();

  if (!t->is_VM_thread()) {
    SkipGCALot sgcalot(t);    // avoid re-entrant attempts to gc-a-lot
    // JavaThread or WatcherThread
    bool concurrent = op->evaluate_concurrently();
    // only blocking VM operations need to verify the caller's safepoint state:
    if (!concurrent) {
      t->check_for_valid_safepoint_state(true);
    }

    // New request from Java thread, evaluate prologue
    if (!op->doit_prologue()) {
      return;   // op was cancelled
    }

    // Setup VM_operations for execution
    op->set_calling_thread(t, Thread::get_priority(t));

    // It does not make sense to execute the epilogue, if the VM operation object is getting
    // deallocated by the VM thread.
    bool execute_epilog = !op->is_cheap_allocated();
    assert(!concurrent || op->is_cheap_allocated(), "concurrent => cheap_allocated");

    // Get ticket number for non-concurrent VM operations
    int ticket = 0;
    if (!concurrent) {
      ticket = t->vm_operation_ticket();
    }

    // Add VM operation to list of waiting threads. We are guaranteed not to block while holding the
    // VMOperationQueue_lock, so we can block without a safepoint check. This allows vm operation requests
    // to be queued up during a safepoint synchronization.
    {
      VMOperationQueue_lock->lock_without_safepoint_check();
      bool ok = _vm_queue->add(op);
    op->set_timestamp(os::javaTimeMillis());
      VMOperationQueue_lock->notify();
      VMOperationQueue_lock->unlock();
      // VM_Operation got skipped
      if (!ok) {
        assert(concurrent, "can only skip concurrent tasks");
        if (op->is_cheap_allocated()) delete op;
        return;
      }
    }

    if (!concurrent) {
      // Wait for completion of request (non-concurrent)
      // Note: only a JavaThread triggers the safepoint check when locking
      MutexLocker mu(VMOperationRequest_lock);
      while(t->vm_operation_completed_count() < ticket) {
        VMOperationRequest_lock->wait(!t->is_Java_thread());
      }
    }

    if (execute_epilog) {
      op->doit_epilogue();
    }
  } else {
    // invoked by VM thread; usually nested VM operation
    assert(t->is_VM_thread(), "must be a VM thread");
    VM_Operation* prev_vm_operation = vm_operation();
    if (prev_vm_operation != NULL) {
      // Check the VM operation allows nested VM operation. This normally not the case, e.g., the compiler
      // does not allow nested scavenges or compiles.
      if (!prev_vm_operation->allow_nested_vm_operations()) {
        fatal(err_msg("Nested VM operation %s requested by operation %s",
                      op->name(), vm_operation()->name()));
      }
      op->set_calling_thread(prev_vm_operation->calling_thread(), prev_vm_operation->priority());
    }

    EventMark em("Executing %s VM operation: %s", prev_vm_operation ? "nested" : "", op->name());

    // Release all internal handles after operation is evaluated
    HandleMark hm(t);
    _cur_vm_operation = op;

    if (op->evaluate_at_safepoint() && !SafepointSynchronize::is_at_safepoint()) {
      SafepointSynchronize::begin();
      op->evaluate();
      SafepointSynchronize::end();
    } else {
      op->evaluate();
    }

    // Free memory if needed
    if (op->is_cheap_allocated()) delete op;

    _cur_vm_operation = prev_vm_operation;
  }
}

进入队列之后的 op 们,会在 VM_Thread run方法 的 loop 循环中被取出执行,当然,这个队列被取出时要加锁 MutexLockerEx(具体通过pthread_cond_wait等pthread库函数实现)

run方法是在新的操作系统线程中执行的。

具体在Threads::create_vm中体现:

具体os是怎么开始run方法的?

  起始是有一个 叫 java_start 的方法,调用了 run 方法

  然后 os_start 调用 pthread_start 创建一个操作系统层面的线程去执行 java_start


在JVM中定义了线程的类型,在hotspot/src/share/vm/runtime/os.hpp文件中,以枚举的形式定义

  // 线程类型。JVM层面的抽象。
  enum ThreadType {
    vm_thread,          // JVM内部工作线程 
    cgc_thread,         // 并发GC线程
    pgc_thread,         // 并行GC线程
    java_thread,        // java层面定义的线程
    compiler_thread,    // Jit编译线程
    watcher_thread,     // JVM内部的定时处理
    os_thread           // 操作系用的线程
  };

谈到作用,不得不再引入一个知识点,那就是安全点(SafePoint),安全点是JVM提出让工作线程阻塞的想法的落地实现,而为什么工作线程需要阻塞,也很简单,比如GC的时候需要SWT,那么工作线程就需要阻塞等待。安全点的实现也很简单,当需要工作线程阻塞的时候就启动安全点,而工作线程在不同的时期需要去检测是否开启了安全点,如果安全点开启了,当前工作线程就需要去阻塞等待被唤醒。

而安全点的触发工作就是"VMThread"线程来做,并且此线程还会执行需要安全点才能执行的工作,比如:GC垃圾回收、dump 线程堆栈数据 等等需要安全点的操作。

jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
 
    …………
 
    // 创建VMThread
    { 
        TraceTime timer("Start VMThread", TraceStartupTime);
        // 创建VMThread对象,因为当前C++层面,所以也有对象的抽象,好比在Java层面创建Thread对象。
        VMThread::create();
        Thread* vmthread = VMThread::vm_thread();
 
        // 创建底层真正的线程(PThread线程库)。
        if (!os::create_thread(vmthread, os::vm_thread))
            vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");
 
 
        // 启动VMThread,并且等待VMThread中必要部分初始化完毕。
        {
            MutexLocker ml(Notify_lock);
            os::start_thread(vmthread);
            while (vmthread->active_handles() == NULL) {
                Notify_lock->wait();
            }
        }
    }
 
    …………
}

hotspot/src/share/vm/runtime/thread.cpp 文件中create_vm方法是启动并初始化JVM个个模块,其中就初始化了VMThread线程。代码非常的简单,就是创建、启动、初始化VMThread线程。

上述已经介绍完VMThread线程的创建并且运行起来了,所以我们需要找到运行代码。

hotspot/src/share/vm/runtime/vmThread.cpp文件中。

void VMThread::run() {
    …………
    // 执行队列传输事件。
    this->loop();
    …………
}
 
// 死循环处理队列来的任务。
void VMThread::loop() {
  assert(_cur_vm_operation == NULL, "no current one should be executing");
 
  while(true) {
    VM_Operation* safepoint_ops = NULL;
 
 
    //
    // Wait for VM operation
    //
 
    …………
 
    //
    // Execute VM operation
    //
    {
 
      if (_cur_vm_operation->evaluate_at_safepoint()) {
 
        // 进入线程安全点,准备做事。
        SafepointSynchronize::begin();
        // 执行任务
        evaluate_operation(_cur_vm_operation);
 
        do {
          _cur_vm_operation = safepoint_ops;
          if (_cur_vm_operation != NULL) {
            do {
 
              VM_Operation* next = _cur_vm_operation->next();
              _vm_queue->set_drain_list(next);
              // 执行任务
              evaluate_operation(_cur_vm_operation);
              _cur_vm_operation = next;
              if (PrintSafepointStatistics) {
                SafepointSynchronize::inc_vmop_coalesced_count();
              }
            } while (_cur_vm_operation != NULL);
          }
 
        } while(safepoint_ops != NULL);
 
        _vm_queue->set_drain_list(NULL);
 
        // Complete safepoint synchronization
        SafepointSynchronize::end();
 
      } 
    }
 
    …………
  }
}

此代码量特别大,所以笔者删减了很多跟主流程无关代码,这样方便读者阅读,减轻难度。

大致流程如下:

死循环一直处理任务,直到JVM关闭
阻塞等待VMOperationQueue队列来任务
开启线程安全点
处理VMOperationQueue队列的VM_Operation任务
关闭线程安全点
进入到下一次的阻塞等待,周而复始
所以,我们还需要哪里给VMOperationQueue队列投递的VM_Operation任务

我们以dump thread线程堆栈数据来作为展示。

hotspot/src/share/vm/services/attachListener.cpp文件中。

static jint thread_dump(AttachOperation* op, outputStream* out) {
 
  // thread stacks
  VM_PrintThreads op1(out, print_concurrent_locks);
  VMThread::execute(&op1);
 
  // JNI global handles
  VM_PrintJNI op2(out);
  VMThread::execute(&op2);
 
  // Deadlock detection
  VM_FindDeadlocks op3(out);
  VMThread::execute(&op3);
 
  return JNI_OK;
}
 
void VMThread::execute(VM_Operation* op) {
  Thread* t = Thread::current();
 
  …………
 
  if (!t->is_VM_thread()) {            // 当前不是VMThread线程,所以需要把VM_Operation通过Queue的方式传给VMThread线程去处理。
    {
      VMOperationQueue_lock->lock_without_safepoint_check();
      // 将任务添加到VMOperationQueue队列,然后尝试唤醒VMThread线程。
      bool ok = _vm_queue->add(op);
      op->set_timestamp(os::javaTimeMillis());
      VMOperationQueue_lock->notify();
      VMOperationQueue_lock->unlock();
    }
  } else {                            // 当前已经是VMThread线程了,所以可以直接执行。
 
      …………
 
    // 如果当前已经是VMThread在执行此代码,那就直接执行
    if (op->evaluate_at_safepoint() && !SafepointSynchronize::is_at_safepoint()) {
      // 安全点的设置。
      SafepointSynchronize::begin();
      // 执行任务
      op->evaluate();
      SafepointSynchronize::end();
    } else {
      op->evaluate();
    }
    _cur_vm_operation = prev_vm_operation;
  }
}

这里把VM_Operation任务添加到VMOperationQueue队列中,并且尝试把VMThread给唤醒。此时VMThread线程醒来后,又开始走上面的周而复始的流程了。

原文

关于构建Java VM调试环境的总结 - 知乎 (zhihu.com)

JVM源码分析VMThread线程_jvm 源码-CSDN博客

Last modification:November 27, 2023
如果觉得我的文章对你有用,请随意赞赏