思想至上
大多数Java程序员对线程的感知度很低、少部分Java程序员明白在适合的业务项目中使用线程池完成异步操作,也明白在使用Java的Spring系列框架开发后端程序请求是从Tomcat的工作线程池最终到用户Servlet业务逻辑、极少部分的Java程序员明白除开Java层面,底层JVM也有众多线程在工作。
而此篇文章就带各位读者分析在JVM中默默工作的"VMThread"线程。
笔者会从几个问题分析:
- VMThread何时创建?
- VMThread与Java程序创建的线程有什么区别?
- VMThread作用?
问题1 :
肯定是在JVM启动的时候去创建的VMThread线程
问题2:
在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 // 操作系用的线程
};
这里定义了JVM内部所能出现的线程类型,而VMThread和Java程序创建的线程区别就一目了然了,一个是JVM内部工作的线程,一个是Java程序使用的线程。相同点就是他两都是线程....
问题3:
谈到作用,不得不再引入一个知识点,那就是安全点(SafePoint),安全点是JVM提出让工作线程阻塞的想法的落地实现,而为什么工作线程需要阻塞,也很简单,比如GC的时候需要SWT,那么工作线程就需要阻塞等待。安全点的实现也很简单,当需要工作线程阻塞的时候就启动安全点,而工作线程在不同的时期需要去检测是否开启了安全点,如果安全点开启了,当前工作线程就需要去阻塞等待被唤醒。
而安全点的触发工作就是"VMThread"线程来做,并且此线程还会执行需要安全点才能执行的工作,比如:GC垃圾回收、dump 线程堆栈数据 等等需要安全点的操作。
源码论证
下面代码选用JDK1.8版本。Hotspot虚拟机源码。
问题1:
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线程。
问题3:
上述已经介绍完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线程醒来后,又开始走上面的周而复始的流程了。
总结
思想至上,源码作为论证过程