java 安全点机制解析(编译执行)

2023-01-08  本文已影响0人  allanYan

概述

我们都知道JVM中有很多操作,都是要求线程达到安全点,例如典型的垃圾回收,本文将深入细节,了解JVM的具体实现;
JAVA代码一开始通常是采用解释执行,当执行一定次数,发现是属于热点代码时,会通过JIT编译为机器代码,提供执行效率;
JIT编译时会插入一些安全点检查的指令,会查询当前Java线程的polling_page,正常情况下,查询polling_page没有什么问题;但是当VM线程开始执行时,会将polling_page改为bad page;在linux系统中,访问不可读页面会产生SIGSEGV信号:

JIT插入安全点指令

//c1_LIRAssembler.cpp:LIR_Assembler::emit_op1->LIR_Assembler::safepoint_poll
int LIR_Assembler::safepoint_poll(LIR_Opr tmp, CodeEmitInfo* info) {
  int offset = __ offset();
#ifdef _LP64
  const Register poll_addr = rscratch1;
  __ movptr(poll_addr, Address(r15_thread, JavaThread::polling_page_offset()));
#else
  const Register poll_addr = tmp->as_register();
  __ get_thread(poll_addr);
  __ movptr(poll_addr, Address(poll_addr, in_bytes(JavaThread::polling_page_offset())));
#endif
  add_debug_info_for_branch(info);
  __ relocate(relocInfo::poll_type);
  address pre_pc = __ pc();
  __ testl(rax, Address(poll_addr, 0));
  address post_pc = __ pc();
  return offset;
}

关键代码是这里的 __ movptr(poll_addr, Address(r15_thread, JavaThread::polling_page_offset()))

polling_page

polling_page到底是什么东西呢,可以从JVM的启动代码里面看到其初始化流程

//Threads::create_vm->SafepointMechanism::initialize()->SafepointMechanism::default_initialize

    const size_t page_size = os::vm_page_size();
    const size_t allocation_size = 2 * page_size;
    char* polling_page = os::reserve_memory(allocation_size);
    os::commit_memory_or_exit(polling_page, allocation_size, false, "Unable to commit Safepoint polling page");
    MemTracker::record_virtual_memory_type((address)polling_page, mtSafepoint);

    char* bad_page  = polling_page;
    char* good_page = polling_page + page_size;

    os::protect_memory(bad_page,  page_size, os::MEM_PROT_NONE);
    os::protect_memory(good_page, page_size, os::MEM_PROT_READ);

    log_info(os)("SafePoint Polling address, bad (protected) page:" INTPTR_FORMAT ", good (unprotected) page:" INTPTR_FORMAT, p2i(bad_page), p2i(good_page));

    // Poll address values
    _poll_page_armed_value    = reinterpret_cast<uintptr_t>(bad_page);
    _poll_page_disarmed_value = reinterpret_cast<uintptr_t>(good_page);
    _polling_page = (address)bad_page;

可以看到JVM启动时,申请了两个页,一个是可读的(good page),一个不可读(bad page);

VM线程

JVM中进入安全点的典型流程是通过VM线程,代码如下:

//vmThread.cpp:VMThread::inner_execute->SafepointSynchronize::begin->SafepointSynchronize::arm_safepoint

 for (JavaThreadIteratorWithHandle jtiwh; JavaThread *cur = jtiwh.next(); ) {
    // Make sure the threads start polling, it is time to yield.
    SafepointMechanism::arm_local_poll(cur);
  }
void SafepointMechanism::arm_local_poll(JavaThread* thread) {
  thread->poll_data()->set_polling_word(_poll_word_armed_value);
  thread->poll_data()->set_polling_page(_poll_page_armed_value);
}

进入安全点时,将将所有JAVA线程的_polling_page指向不可读页面(bad page);在linux系统中,访问一个MEM_PROT_NONE的页面会产生SIGSEGV;
而在JVM启动过程中,SIGSEGV信号已经被注册给了SafepointSynchronize::handle_polling_page_exception函数:

注册SIGSEGV信号

//os_linux_x86.cpp
bool PosixSignals::pd_hotspot_signal_handler(int sig, siginfo_t* info,
                                             ucontext_t* uc, JavaThread* thread) {
......
 if (sig == SIGSEGV && SafepointMechanism::is_poll_address((address)info->si_addr)) {
        stub = SharedRuntime::get_poll_stub(pc);
      }
......
}
address SharedRuntime::get_poll_stub(address pc) {
 bool at_poll_return = ((CompiledMethod*)cb)->is_at_poll_return(pc);
  bool has_wide_vectors = ((CompiledMethod*)cb)->has_wide_vectors();
  if (at_poll_return) {
    stub = SharedRuntime::polling_page_return_handler_blob()->entry_point();
  } else if (has_wide_vectors) {
    stub = SharedRuntime::polling_page_vectors_safepoint_handler_blob()->entry_point();
  } else {
    stub = SharedRuntime::polling_page_safepoint_handler_blob()->entry_point();
  }
}

```c
void SharedRuntime::generate_stubs() {
......
_polling_page_safepoint_handler_blob = generate_handler_blob(CAST_FROM_FN_PTR(address, SafepointSynchronize::handle_polling_page_exception), POLL_AT_LOOP);
  _polling_page_return_handler_blob    = generate_handler_blob(CAST_FROM_FN_PTR(address, SafepointSynchronize::handle_polling_page_exception), POLL_AT_RETURN);

......
}

SIGSEGV信号处理

ThreadSafepointState::handle_polling_page_exception最终通过SafepointSynchronize::block暂停业务线程:

if (global_poll()) {
      // Any load in ::block() must not pass the global poll load.
      // Otherwise we might load an old safepoint counter (for example).
      OrderAccess::loadload();
      SafepointSynchronize::block(thread);
    }

至此,安全点的触发机制都已经完全了解清楚;

上一篇 下一篇

猜你喜欢

热点阅读