[Home]   [TOC]

Study_Java_HotSpot_Thread_VMThread  
VM_Thread of hotspot
Updated Aug 31, 2012 by jht5...@gmail.com
class VM_Operation: public CHeapObj

safepoint介绍 [1]

safe point 顾明思意,就是安全点,当需要jvm做一些操作的时候,需要把当前正在运行的线程进入一个安全点的状态(也可以说停止状态),这样才能做一些安全的操作,比如线程的dump,堆栈的信息。

在jvm里面通常vm_thread(我们一直在谈论的做一些属于vm 份内事情的线程) 和cms_thread(内存回收的线程)做的操作,是需要将其他的线程通过调用SafepointSynchronize::beginSafepointSynchronize:end来实现让其他的线程进入或者退出safe point 的状态。

通常safepoint的有三种状态 [1]

_not_synchronized 说明没有任何打断现在所有线程运行的操作,也就是vm thread, cms thread 没有接到操作的指令
_synchronizing vm thread,cms thread 接到操作指令,正在等待所有线程进入safe point
_synchronized 所有线程进入safe point, vm thread, cms thread 可以开始指令操作

Java线程的状态 [1]

通常在java 进程中的Java 的线程有几个不同的状态,如何让这些线程进入safepoint 的状态中,jvm是采用不同的方式

a. 正在解释执行

由于java是解释性语言,而线程在解释java 字节码的时候,需要dispatch table,记录方法地址进行跳转的,那么这样让线程进入停止状态就比较容易了,只要替换掉dispatch table 就可以了,让线程知道当前进入softpoint 状态。
java里会设置3个DispatchTable,_active_table, _normal_table, _safept_table

_active_table 正在解释运行的线程使用的dispatch table
_normal_table 就是正常运行的初始化的dispatch table
_safept_table safe point需要的dispatch table

解释运行的线程一直都在使用_active_table,关键处就是在进入saftpoint 的时候,用_safept_table替换_active_table, 在退出saftpoint 的时候,使用_normal_table来替换_active_table
Source Code:

void TemplateInterpreter::notice_safepoints() {  
  if (!_notice_safepoints) {  
    // switch to safepoint dispatch table  
    _notice_safepoints = true;  
    copy_table((address*)&_safept_table, (address*)&_active_table, sizeof(_active_table) / sizeof(address));  
  }  
}
  
// switch from the dispatch table which notices safepoints back to the  
// normal dispatch table.  So that we can notice single stepping points,  
// keep the safepoint dispatch table if we are single stepping in JVMTI.  
// Note that the should_post_single_step test is exactly as fast as the  
// JvmtiExport::_enabled test and covers both cases.  
void TemplateInterpreter::ignore_safepoints() {  
  if (_notice_safepoints) {  
    if (!JvmtiExport::should_post_single_step()) {  
      // switch to normal dispatch table  
      _notice_safepoints = false;  
      copy_table((address*)&_normal_table, (address*)&_active_table, sizeof(_active_table) / sizeof(address));  
    }  
  }  
}

b. 运行在native code

如果线程运行在native code的时候,vm thread 是不需要等待线程执行完的,只需要在从native code 返回的时候去判断一下 state 的状态就可以了。

在方法体里就是前面博客也出现过的 SafepointSynchronize::do_call_back()

inline static bool do_call_back() {  
  return (_state != _not_synchronized);  
}

判断了_state 不是_not_synchronized状态

为了能让线程从native code 回到java 的时候为了能读到/设置正确线程的状态,通常的解决方法使用memory barrier,java 使用OrderAccess::fence(); 在汇编里使用 __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory"); 保证从内存里读到正确的值,但是这种方法严重影响系统的性能,于是java使用了每个线程都有独立的内存页来设置状态。通过使用使用参数-XX:+UseMembar 参数使用memory barrier,默认是不打开的,也就是使用独立的内存页来设置状态。

c. 运行编译的代码

1. Poling page 页面

Poling page是在jvm初始化启动的时候会初始化的一个单独的内存页面,这个页面是让运行的编译过的代码的线程进入停止状态的关键。

在linux里面使用了mmap初始化,源码如下

address polling_page = (address) ::mmap(NULL, Linux::page_size(), PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
2. 编译

java 的JIT 会直接编译一些热门的源码到机器码,直接执行而不需要在解释执行从而提高效率,在编译的代码中,当函数或者方法块返回的时候会去访问一个内存poling页面.

x86架构下

void LIR_Assembler::return_op(LIR_Opr result) {  
  assert(result->is_illegal() || !result->is_single_cpu() || result->as_register() == rax, "word returns are in rax,");  
  if (!result->is_illegal() && result->is_float_kind() && !result->is_xmm_register()) {  
    assert(result->fpu() == 0, "result must already be on TOS");  
  }  
  
  // Pop the stack before the safepoint code  
  __ remove_frame(initial_frame_size_in_bytes());  
  
  bool result_is_oop = result->is_valid() ? result->is_oop() : false;  
  
  // Note: we do not need to round double result; float result has the right precision  
  // the poll sets the condition code, but no data registers  
  AddressLiteral polling_page(os::get_polling_page() + (SafepointPollOffset % os::vm_page_size()),  
                              relocInfo::poll_return_type);  
  
  // NOTE: the requires that the polling page be reachable else the reloc  
  // goes to the movq that loads the address and not the faulting instruction  
  // which breaks the signal handler code  
  
  __ test32(rax, polling_page);  
  
  __ ret(0);  
}

在前面提到的 SafepointSynchronize::begin 函数源码中

if (UseCompilerSafepoints && DeferPollingPageLoopCount < 0) {  
  // Make polling safepoint aware  
  guarantee (PageArmed == 0, "invariant") ;  
  PageArmed = 1 ;  
  os::make_polling_page_unreadable();  
}

这里提到了2个参数 UseCompilerSafepointsDeferPollingPageLoopCount ,在默认的情况下这2个参数是true和-1

函数体将会调用 os:make_polling_page_unreadable(); 在linux os 下具体实现是调用了 mprotect(bottom,size,prot) 使polling 内存页变成不可读。

3. 信号

到当编译好的程序尝试在去访问这个不可读的polling页面的时候,在系统级别会产生一个错误信号SIGSEGV, 可以参考笔者的一篇博客中曾经讲过java 的信号处理,可以知道信号SIGSEGV的处理函数在x86体系下见下源码:

JVM_handle_linux_signal(int sig,  
                        siginfo_t* info,  
                        void* ucVoid,  
                        int abort_if_unrecognized){  
   ....  
   if (sig == SIGSEGV && os::is_poll_address((address)info->si_addr)) {  
        stub = SharedRuntime::get_poll_stub(pc);  
      }   
   ....  
}

linux x86,64 bit的体系中,poll stub 的地址 就是 SafepointSynchronize::handle_polling_page_exception 详细程序可见 shareRuntime_x86_64.cpp

回到safepoint.cpp中,SafepointSynchronize::handle_polling_page_exception 通过取出线程的safepoint_stat,调用函数 void ThreadSafepointState::handle_polling_page_exception,最后通过调用 SafepointSynchronize::block(thread()); 来block当前线程。

d. block 状态

当线程进入block状态的时候,继续保持block状态。


enum Priorities {
   SafepointPriority, // Highest priority (operation executed at a safepoint)
   MediumPriority,    // Medium priority
   nof_priorities
};

enum Mode {
  _safepoint,       // blocking,        safepoint, vm_op C-heap allocated
  _no_safepoint,    // blocking,     no safepoint, vm_op C-Heap allocated
  _concurrent,      // non-blocking, no safepoint, vm_op C-Heap allocated
  _async_safepoint  // non-blocking,    safepoint, vm_op C-Heap allocated
};

enum VMOp_Type {
  VM_OPS_DO(VM_OP_ENUM)
  VMOp_Terminating
};

VM_Operation blocking safepoint 详细说明
VM_Dummy - - 虚拟的VM operation,在循环双链表中作为第一个元素
VM_CMS_Operation
VM_CMS_Initial_Mark
VM_CMS_Final_Remark
VM_CGC_Operation
VM_GC_Operation
VM_HeapDumper Dump内存到文件,以下情况会调用:
* OutOfMemoryError && -XX:+HeapDumpOnOutOfMemoryError
* Full GC && -XX:+HeapDumpBeforeFullGC
* Full GC && -XX:+HeapDumpAfterFullGC
* HotSpotDiagnostic#dumpHeap
* HotSpotVirtualMachine#dumpHeap(如通过命令:jmap -dump)
VM_GenCollectFullConcurrent
VM_G1CollectFull
VM_G1CollectForAllocation
VM_G1IncCollectionPause
VM_ParallelGCFailedAllocation
VM_ParallelGCFailedPermanentAllocation
VM_ParallelGCSystemGC
VM_GC_HeapInspection Dump内存Histogram到文件,以下情况会调用:
* kill -3
* Full GC && -XX:+PrintClassHistogramBeforeFullGC
* Full GC && -XX:+PrintClassHistogramAfterFullGC
* HotSpotVirtualMachine#heapHisto(如通过命令:jmap -histo)
VM_GenCollectForAllocation
VM_GenCollectFull
VM_GenCollectForPermanentAllocation
VM_PopulateDumpSharedSpace
VM_JNIFunctionTableCopier
VM_GetOwnedMonitorInfo
VM_GetObjectMonitorUsage
VM_GetCurrentContendedMonitor
VM_GetStackTrace
VM_GetMultipleStackTraces
VM_GetFrameCount
VM_GetFrameLocation
VM_EnterInterpOnlyMode
VM_ChangeSingleStep
VM_GetCurrentLocation
VM_ChangeBreakpoints
VM_RedefineClasses
VM_GetOrSetLocal
VM_HeapIterateOperation
VM_HeapWalkOperation
VM_EnableBiasedLocking is_cheap_allocated?
non-blocking:
blocking
safepoint
VM_RevokeBias 取消偏向锁,如计算HashCode,锁竞争等
VM_BulkRevokeBias
VM_ThreadStop
VM_ForceSafepoint
VM_ForceAsyncSafepoint
VM_Deoptimize
VM_DeoptimizeFrame
VM_HandleFullCodeCache
VM_DeoptimizeAll
VM_ZombieAll
VM_Verify
VM_PrintThreads
VM_PrintJNI
VM_FindDeadlocks 查找死锁线程
VM_ThreadDump
VM_Exit 退出JVM
VM_DeoptimizeTheWorld
VM_ReportJavaOutOfMemory 在OOM时执行Shell命令,通过参数-XX:OnOutOfMemoryError=设置

参将资料

[1]. http://blog.csdn.net/raintungli/article/details/7162468