轮询页的不可访问性是在 JVM 启动初始化阶段静态设置 的,具体由以下函数完成:
核心函数:os::protect_memory
bool os::protect_memory(char* addr, size_t bytes, ProtType prot, bool is_committed) { unsigned int p = 0; switch (prot) { case MEM_PROT_NONE: p = PROT_NONE; break; // 设置为不可访问 case MEM_PROT_READ: p = PROT_READ; break; // ...其他权限设置 } return linux_mprotect(addr, bytes, p); // 调用系统调用修改内存权限 }
- 功能:通过
mprotect
系统调用修改指定内存区域的权限。 - 调用链:在 JVM 启动时由
SafepointMechanism::default_initialize()
调用。
初始化阶段设置轮询页权限
在 SafepointMechanism::default_initialize()
中:
// 分配两个连续页:bad_page(不可访问)和 good_page(可读) os::protect_memory(bad_page, page_size, os::MEM_PROT_NONE); // 设置 bad_page 不可访问 os::protect_memory(good_page, page_size, os::MEM_PROT_READ); // 设置 good_page 可读
- 静态配置:此时
bad_page
被永久设置为 不可访问,good_page
设置为可读。 - 后续使用:
- 当需要进入安全点时,JVM 让线程访问
bad_page
(通过修改线程的轮询地址),触发 SIGSEGV 信号。 - 安全点结束后,线程恢复访问
good_page
。
- 当需要进入安全点时,JVM 让线程访问
动态触发安全点的机制
在安全点开始时,通过以下函数 切换线程的轮询地址 到 bad_page
(而不是修改内存权限):
void SafepointMechanism::arm_local_poll(JavaThread* thread) { // 将线程的轮询地址设为 _poll_page_armed_value(即 bad_page) thread->set_polling_page(_poll_page_armed_value); }
- 不修改内存权限:轮询页的权限在初始化时已固定,JVM 通过切换线程访问的目标地址(
bad_page
或good_page
)控制安全点触发。
总结
- 设置轮询页不可访问的函数:
os::protect_memory
(在 JVM 启动时调用)。 - 触发安全点的机制:通过
arm_local_poll()
将线程的轮询地址指向预先不可访问的bad_page
,而非动态修改内存权限。
##源码
/ Set protections specified
bool os::protect_memory(char* addr, size_t bytes, ProtType prot,
bool is_committed) {
unsigned int p = 0;
switch (prot) {
case MEM_PROT_NONE: p = PROT_NONE; break;
case MEM_PROT_READ: p = PROT_READ; break;
case MEM_PROT_RW: p = PROT_READ|PROT_WRITE; break;
case MEM_PROT_RWX: p = PROT_READ|PROT_WRITE|PROT_EXEC; break;
default:
ShouldNotReachHere();
}
// is_committed is unused.
return linux_mprotect(addr, bytes, p);
}
int SafepointSynchronize::synchronize_threads(jlong safepoint_limit_time, int nof_threads, int* initial_running)
{
JavaThreadIteratorWithHandle jtiwh;
#ifdef ASSERT
for (; JavaThread *cur = jtiwh.next(); ) {
assert(cur->safepoint_state()->is_running(), "Illegal initial state");
}
jtiwh.rewind();
#endif // ASSERT
// Iterate through all threads until it has been determined how to stop them all at a safepoint.
int still_running = nof_threads;
ThreadSafepointState *tss_head = NULL;
ThreadSafepointState **p_prev = &tss_head;
for (; JavaThread *cur = jtiwh.next(); ) {
ThreadSafepointState *cur_tss = cur->safepoint_state();
assert(cur_tss->get_next() == NULL, "Must be NULL");
if (thread_not_running(cur_tss)) {
--still_running;
} else {
*p_prev = cur_tss;
p_prev = cur_tss->next_ptr();
}
}
*p_prev = NULL;
DEBUG_ONLY(assert_list_is_valid(tss_head, still_running);)
*initial_running = still_running;
// If there is no thread still running, we are already done.
if (still_running <= 0) {
assert(tss_head == NULL, "Must be empty");
return 1;
}
int iterations = 1; // The first iteration is above.
int64_t start_time = os::javaTimeNanos();
do {
// Check if this has taken too long:
if (SafepointTimeout && safepoint_limit_time < os::javaTimeNanos()) {
print_safepoint_timeout();
}
p_prev = &tss_head;
ThreadSafepointState *cur_tss = tss_head;
while (cur_tss != NULL) {
assert(cur_tss->is_running(), "Illegal initial state");
if (thread_not_running(cur_tss)) {
--still_running;
*p_prev = NULL;
ThreadSafepointState *tmp = cur_tss;
cur_tss = cur_tss->get_next();
tmp->set_next(NULL);
} else {
*p_prev = cur_tss;
p_prev = cur_tss->next_ptr();
cur_tss = cur_tss->get_next();
}
}
DEBUG_ONLY(assert_list_is_valid(tss_head, still_running);)
if (still_running > 0) {
back_off(start_time);
}
iterations++;
} while (still_running > 0);
assert(tss_head == NULL, "Must be empty");
return iterations;
}
void SafepointSynchronize::arm_safepoint() {
// Begin the process of bringing the system to a safepoint.
// Java threads can be in several different states and are
// stopped by different mechanisms:
//
// 1. Running interpreted
// When executing branching/returning byte codes interpreter
// checks if the poll is armed, if so blocks in SS::block().
// 2. Running in native code
// When returning from the native code, a Java thread must check
// the safepoint _state to see if we must block. If the
// VM thread sees a Java thread in native, it does
// not wait for this thread to block. The order of the memory
// writes and reads of both the safepoint state and the Java
// threads state is critical. In order to guarantee that the
// memory writes are serialized with respect to each other,
// the VM thread issues a memory barrier instruction.
// 3. Running compiled Code
// Compiled code reads the local polling page that
// is set to fault if we are trying to get to a safepoint.
// 4. Blocked
// A thread which is blocked will not be allowed to return from the
// block condition until the safepoint operation is complete.
// 5. In VM or Transitioning between states
// If a Java thread is currently running in the VM or transitioning
// between states, the safepointing code will poll the thread state
// until the thread blocks itself when it attempts transitions to a
// new state or locking a safepoint checked monitor.
// We must never miss a thread with correct safepoint id, so we must make sure we arm
// the wait barrier for the next safepoint id/counter.
// Arming must be done after resetting _current_jni_active_count, _waiting_to_block.
_wait_barrier->arm(static_cast<int>(_safepoint_counter + 1));
assert((_safepoint_counter & 0x1) == 0, "must be even");
// The store to _safepoint_counter must happen after any stores in arming.
Atomic::release_store(&_safepoint_counter, _safepoint_counter + 1);
// We are synchronizing
OrderAccess::storestore(); // Ordered with _safepoint_counter
_state = _synchronizing;
// Arming the per thread poll while having _state != _not_synchronized means safepointing
log_trace(safepoint)("Setting thread local yield flag for threads");
OrderAccess::storestore(); // storestore, global state -> local state
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *cur = jtiwh.next(); ) {
// Make sure the threads start polling, it is time to yield.
SafepointMechanism::arm_local_poll(cur);
}
OrderAccess::fence(); // storestore|storeload, global state -> local state
}
// Roll all threads forward to a safepoint and suspend them all
void SafepointSynchronize::begin() {
assert(Thread::current()->is_VM_thread(), "Only VM thread may execute a safepoint");
EventSafepointBegin begin_event;
SafepointTracing::begin(VMThread::vm_op_type());
Universe::heap()->safepoint_synchronize_begin();
// By getting the Threads_lock, we assure that no threads are about to start or
// exit. It is released again in SafepointSynchronize::end().
Threads_lock->lock();
assert( _state == _not_synchronized, "trying to safepoint synchronize with wrong state");
int nof_threads = Threads::number_of_threads();
_nof_threads_hit_polling_page = 0;
log_debug(safepoint)("Safepoint synchronization initiated using %s wait barrier. (%d threads)", _wait_barrier->description(), nof_threads);
// Reset the count of active JNI critical threads
_current_jni_active_count = 0;
// Set number of threads to wait for
_waiting_to_block = nof_threads;
jlong safepoint_limit_time = 0;
if (SafepointTimeout) {
// Set the limit time, so that it can be compared to see if this has taken
// too long to complete.
safepoint_limit_time = SafepointTracing::start_of_safepoint() + (jlong)SafepointTimeoutDelay * (NANOUNITS / MILLIUNITS);
timeout_error_printed = false;
}
EventSafepointStateSynchronization sync_event;
int initial_running = 0;
// Arms the safepoint, _current_jni_active_count and _waiting_to_block must be set before.
arm_safepoint();
// Will spin until all threads are safe.
int iterations = synchronize_threads(safepoint_limit_time, nof_threads, &initial_running);
assert(_waiting_to_block == 0, "No thread should be running");
#ifndef PRODUCT
// Mark all threads
if (VerifyCrossModifyFence) {
JavaThreadIteratorWithHandle jtiwh;
for (; JavaThread *cur = jtiwh.next(); ) {
cur->set_requires_cross_modify_fence(true);
}
}
if (safepoint_limit_time != 0) {
jlong current_time = os::javaTimeNanos();
if (safepoint_limit_time < current_time) {
log_warning(safepoint)("# SafepointSynchronize: Finished after "
INT64_FORMAT_W(6) " ms",
(int64_t)(current_time - SafepointTracing::start_of_safepoint()) / (NANOUNITS / MILLIUNITS));
}
}
#endif
assert(Threads_lock->owned_by_self(), "must hold Threads_lock");
// Record state
_state = _synchronized;
OrderAccess::fence();
// Set the new id
++_safepoint_id;
#ifdef ASSERT
// Make sure all the threads were visited.
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *cur = jtiwh.next(); ) {
assert(cur->was_visited_for_critical_count(_safepoint_counter), "missed a thread");
}
#endif // ASSERT
// Update the count of active JNI critical regions
GCLocker::set_jni_lock_count(_current_jni_active_count);
post_safepoint_synchronize_event(sync_event,
_safepoint_id,
initial_running,
_waiting_to_block, iterations);
SafepointTracing::synchronized(nof_threads, initial_running, _nof_threads_hit_polling_page);
// We do the safepoint cleanup first since a GC related safepoint
// needs cleanup to be completed before running the GC op.
EventSafepointCleanup cleanup_event;
do_cleanup_tasks();
post_safepoint_cleanup_event(cleanup_event, _safepoint_id);
post_safepoint_begin_event(begin_event, _safepoint_id, nof_threads, _current_jni_active_count);
SafepointTracing::cleanup();
}
void SafepointMechanism::default_initialize() {
// Poll bit values
_poll_word_armed_value = poll_bit();
_poll_word_disarmed_value = ~_poll_word_armed_value;
bool poll_bit_only = false;
#ifdef USE_POLL_BIT_ONLY
poll_bit_only = USE_POLL_BIT_ONLY;
#endif
if (poll_bit_only) {
_poll_page_armed_value = poll_bit();
_poll_page_disarmed_value = 0;
} else {
// Polling page
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;
}
}
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);
}
// Caller is responsible for using a memory barrier if needed.
inline void SafepointMechanism::ThreadData::set_polling_page(uintptr_t poll_value) {
Atomic::store(&_polling_page, poll_value);
}