實現一個jstack
在聊Jstack得工作原理前呢,不如讓我們先寫一個簡單的jstack玩玩。不用怕,很簡單的,就幾行代碼的事,看:
public class MyJstack {
public static void main(String[] args)throws Exception {
VirtualMachine virtualMachine = VirtualMachine.attach("6361");
HotSpotVirtualMachine hotSpotVirtualMachine = (HotSpotVirtualMachine)virtualMachine;
InputStream inputStream = hotSpotVirtualMachine.remoteDataDump(new String[]{});
byte[] buff = new byte[256];
int len;
do {
len = inputStream.read(buff);
if (len > 0) {
String respone = new String(buff, 0, len, "UTF-8");
System.out.print(respone);
}
} while(len > 0);
inputStream.close();
virtualMachine.detach();
}
}
很簡單吧,貼到你的開發環境裏,運行就好了,別忘了把6361這個進程號換成你自己的Java進程號哦。
實現原理
jstack有兩種實現方式,一種是基於attach api,其實現可以在tools.jar裏找到;另一種是基於SA的實現,它被放在了sa-jdi.jar裏。如果你通過idea搜索Jstack類,你會看到tools.jar和sa-jdi.jar各有一個Jstack類。
本文呢,就通過分析attch api的源碼,來了解jstack的工作原理。
jstack本地源碼實現
我們來看一下HotSpotVirtualMachine的remoteDataDump方法:
public InputStream remoteDataDump(Object... var1) throws IOException {
return this.executeCommand("threaddump", var1);
}
他是在執行一個叫threaddump的命令。沿着這個executeCommand方法繼續往裏追,會發現他是調用瞭如下方法:
InputStream execute(String var1, Object... var2) throws AgentLoadException, IOException {
assert var2.length <= 3;
String var3;
synchronized(this) {
if (this.path == null) {
throw new IOException("Detached from target VM");
}
var3 = this.path;
}
int var4 = socket();
try {
connect(var4, var3);
} catch (IOException var9) {
close(var4);
throw var9;
}
IOException var5 = null;
try {
this.writeString(var4, "1");
this.writeString(var4, var1);
var1參數就是我們的threaddump指令,不難看出,這個方法是建立了一個socket連接,然後將threaddump指令發送給另一端,即我們要檢查的jvm進程。
注意:限於篇幅我並沒有貼整個方法代碼。execute是HotSpotVirtualMachine的抽象方法,不同平臺的jdk有不同的execute方法的實現,我這裏的代碼是mac下的execute實現,位於BsdVirtualMachine類中。
通過jtack本地源代碼,我們大致可以粗略的認爲:jstack就是通過與指定的jvm進程建立socket連接,然後發送指令,最後將jvm進程返回的內容打印出來。
JVM的源碼實現
瞭解了jstack的本地源碼,我們在看看jvm進程是如何處理的。
當我們使用Java命令啓動jvm進程時,Java命令會加載虛擬機共享庫,然後執行共享庫裏的JNI_CreateJavaVM方法完成虛擬機的創建,在JNI_CreateJavaVM方法裏會調用如下代碼,完成具體的一個創建過程:
result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
如果你有心,或許會留意到,在你啓動一個jvm進程時,即便你什麼線程也沒創建,你用jstack查看還是有很多的線程,如:Signal Dispatcher,VM Thread,Attach Listener等等。當過閱讀本文,你會瞭解到這三個線程的作用。
01 VM Thread線程
Threads::create_vm這個方法很長,接下來咱們跳出一些重要的段落,來分析分析。
// Create the VMThread
{ TraceTime timer("Start VMThread", TraceStartupTime);
VMThread::create();//創建Thread對象
Thread* vmthread = VMThread::vm_thread();
if (!os::create_thread(vmthread, os::vm_thread))//調用操作系統api創建線程
vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");
// Wait for the VM thread to become ready, and VMThread::run to initialize
// Monitors can have spurious returns, must always check another state flag
{
MutexLocker ml(Notify_lock);
os::start_thread(vmthread);//啓動線程
while (vmthread->active_handles() == NULL) {
Notify_lock->wait();
}
}
}
通過註釋,你也知道,這一段代碼是從來創建VM Thread線程的。VMThread::create()完成了對現成的命名工作,代碼如下:
void VMThread::create() {
assert(vm_thread() == NULL, "we can only allocate one VMThread");
_vm_thread = new VMThread();
// Create VM operation queue
_vm_queue = new VMOperationQueue();
guarantee(_vm_queue != NULL, "just checking");
_terminate_lock = new Monitor(Mutex::safepoint, "VMThread::_terminate_lock", true);
if (UsePerfData) {
// jvmstat performance counters
Thread* THREAD = Thread::current();
_perf_accumulated_vm_operation_time =
PerfDataManager::create_counter(SUN_THREADS, "vmOperationTime",
PerfData::U_Ticks, CHECK);
}
}
VMThread::VMThread() : NamedThread() {
set_name("VM Thread");
}
通過new VMThread()創建線程對象,在VMThread的構造方法裏將線程命名成VM Thread,這就是我們jstack看到的VM Thread線程,同時還爲這個線程創建了一個叫VMOperationQueue的隊列。
至於VM Thread線程的作用,我們留到最後再說。
02 Signal Dispatcher線程
繼續沿着 Threads::create_vm方法往下看,我們會看到如下代碼:
// Signal Dispatcher needs to be started before VMInit event is posted
os::signal_init();
這一句代碼實現了Signal Dispatcher線程的創建,進入到signal_init()方法看看:
void os::signal_init() {
if (!ReduceSignalUsage) {
// Setup JavaThread for processing signals
EXCEPTION_MARK;
Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK);
instanceKlassHandle klass (THREAD, k);
instanceHandle thread_oop = klass->allocate_instance_handle(CHECK);
const char thread_name[] = "Signal Dispatcher";
Handle string = java_lang_String::create_from_str(thread_name, CHECK);
// Initialize thread_oop to put it into the system threadGroup
Handle thread_group (THREAD, Universe::system_thread_group());
JavaValue result(T_VOID);
JavaCalls::call_special(&result, thread_oop,
klass,
vmSymbols::object_initializer_name(),
vmSymbols::threadgroup_string_void_signature(),
thread_group,
string,
CHECK);
KlassHandle group(THREAD, SystemDictionary::ThreadGroup_klass());
JavaCalls::call_special(&result,
thread_group,
group,
vmSymbols::add_method_name(),
vmSymbols::thread_void_signature(),
thread_oop, // ARG 1
CHECK);
os::signal_init_pd();
{ MutexLocker mu(Threads_lock);
JavaThread* signal_thread = new JavaThread(&signal_thread_entry);
// At this point it may be possible that no osthread was created for the
// JavaThread due to lack of memory. We would have to throw an exception
// in that case. However, since this must work and we do not allow
// exceptions anyway, check and abort if this fails.
if (signal_thread == NULL || signal_thread->osthread() == NULL) {
vm_exit_during_initialization("java.lang.OutOfMemoryError",
"unable to create new native thread");
}
java_lang_Thread::set_thread(thread_oop(), signal_thread);
java_lang_Thread::set_priority(thread_oop(), NearMaxPriority);
java_lang_Thread::set_daemon(thread_oop());
signal_thread->set_threadObj(thread_oop());
Threads::add(signal_thread);
Thread::start(signal_thread);
}
// Handle ^BREAK
os::signal(SIGBREAK, os::user_handler());
}
}
在這個方法裏,我們可以看到要創建的線程名字:Signal Dispatcher,以及線程啓動後調用的方法signal_thread_entry。(方法較長,看重點就好,沒必要每句話都扣清楚)。
有了對上邊代碼的分析,我們只需要看看signal_thread_entry方法,就知道Signal Dispatcher線程的作用了。
static void signal_thread_entry(JavaThread* thread, TRAPS) {
os::set_priority(thread, NearMaxPriority);
while (true) {
int sig;
{
// FIXME : Currently we have not decieded what should be the status
// for this java thread blocked here. Once we decide about
// that we should fix this.
sig = os::signal_wait();//等待獲取信號
}
if (sig == os::sigexitnum_pd()) {
// Terminate the signal thread
return;
}
switch (sig) {
case SIGBREAK: {
// Check if the signal is a trigger to start the Attach Listener - in that
// case don't print stack traces.
if (!DisableAttachMechanism && AttachListener::is_init_trigger()) {
continue;
}
// Print stack traces
// Any SIGBREAK operations added here should make sure to flush
// the output stream (e.g. tty->flush()) after output. See 4803766.
// Each module also prints an extra carriage return after its output.
VM_PrintThreads op;
VMThread::execute(&op);
VM_PrintJNI jni_op;
VMThread::execute(&jni_op);
VM_FindDeadlocks op1(tty);
VMThread::execute(&op1);
Universe::print_heap_at_SIGBREAK();
if (PrintClassHistogram) {
VM_GC_HeapInspection op1(gclog_or_tty, true /* force full GC before heap inspection */);
VMThread::execute(&op1);
}
if (JvmtiExport::should_post_data_dump()) {
JvmtiExport::post_data_dump();
}
break;
這個方法裏調用os::signal_wait()獲取傳給該jvm進程的信號,然後對信號進行處理。
說下case SIGBREAK裏的處理邏輯,當接收到SIGBREAK信號時,會先判斷是否禁止Attach機制,如果沒有禁止,會調用AttachListener::is_init_trigger()方法觸發Attach Listener線程的初始化.如果attach機制被禁用,則會創建VM_PrintThreads、VM_PrintJNI、VM_FindDeadlocks等代表某一個操作的對象,通過VMThread::execute()方法扔到VM Thread線程的VMOperationQueue隊列。
03 Attach Listener線程
繼續沿着 Threads::create_vm方法往下看,在緊挨着啓動Signal Dispatcher線程的下邊,就是啓動Attach Listener線程的語句:
// Start Attach Listener if +StartAttachListener or it can't be started lazily
if (!DisableAttachMechanism) {
AttachListener::vm_start();
if (StartAttachListener || AttachListener::init_at_startup()) {
AttachListener::init();
}
}
重點就在AttachListener::init()方法裏:
// Starts the Attach Listener thread
void AttachListener::init() {
EXCEPTION_MARK;
Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK);
instanceKlassHandle klass (THREAD, k);
instanceHandle thread_oop = klass->allocate_instance_handle(CHECK);
const char thread_name[] = "Attach Listener";
Handle string = java_lang_String::create_from_str(thread_name, CHECK);
// Initialize thread_oop to put it into the system threadGroup
Handle thread_group (THREAD, Universe::system_thread_group());
JavaValue result(T_VOID);
JavaCalls::call_special(&result, thread_oop,
klass,
vmSymbols::object_initializer_name(),
vmSymbols::threadgroup_string_void_signature(),
thread_group,
string,
THREAD);
if (HAS_PENDING_EXCEPTION) {
tty->print_cr("Exception in VM (AttachListener::init) : ");
java_lang_Throwable::print(PENDING_EXCEPTION, tty);
tty->cr();
CLEAR_PENDING_EXCEPTION;
return;
}
KlassHandle group(THREAD, SystemDictionary::ThreadGroup_klass());
JavaCalls::call_special(&result,
thread_group,
group,
vmSymbols::add_method_name(),
vmSymbols::thread_void_signature(),
thread_oop, // ARG 1
THREAD);
if (HAS_PENDING_EXCEPTION) {
tty->print_cr("Exception in VM (AttachListener::init) : ");
java_lang_Throwable::print(PENDING_EXCEPTION, tty);
tty->cr();
CLEAR_PENDING_EXCEPTION;
return;
}
{ MutexLocker mu(Threads_lock);
JavaThread* listener_thread = new JavaThread(&attach_listener_thread_entry);
// Check that thread and osthread were created
if (listener_thread == NULL || listener_thread->osthread() == NULL) {
vm_exit_during_initialization("java.lang.OutOfMemoryError",
"unable to create new native thread");
}
java_lang_Thread::set_thread(thread_oop(), listener_thread);
java_lang_Thread::set_daemon(thread_oop());
listener_thread->set_threadObj(thread_oop());
Threads::add(listener_thread);
Thread::start(listener_thread);
}
}
我們可以通過代碼看出其創建了一個叫Attach Listener的線程,線程執行的邏輯封裝在了attach_listener_thread_entry方法裏。
Attach Listener線程的作用,我們看看attach_listener_thread_entry方法便知:
static void attach_listener_thread_entry(JavaThread* thread, TRAPS) {
os::set_priority(thread, NearMaxPriority);
thread->record_stack_base_and_size();
if (AttachListener::pd_init() != 0) {
return;
}
AttachListener::set_initialized();
for (;;) {
AttachOperation* op = AttachListener::dequeue();//從隊列裏獲取操作對象
if (op == NULL) {
return; // dequeue failed or shutdown
}
ResourceMark rm;
bufferedStream st;
jint res = JNI_OK;
// handle special detachall operation
if (strcmp(op->name(), AttachOperation::detachall_operation_name()) == 0) {
AttachListener::detachall();
} else {
// find the function to dispatch too
AttachOperationFunctionInfo* info = NULL;
for (int i=0; funcs[i].name != NULL; i++) {
const char* name = funcs[i].name;
assert(strlen(name) <= AttachOperation::name_length_max, "operation <= name_length_max");
if (strcmp(op->name(), name) == 0) {
info = &(funcs[i]);
break;
}
}
// check for platform dependent attach operation
if (info == NULL) {
info = AttachListener::pd_find_operation(op->name());
}
if (info != NULL) {
// dispatch to the function that implements this operation
res = (info->func)(op, &st);//執行操作對象
} else {
st.print("Operation %s not recognized!", op->name());
res = JNI_ERR;
}
}
// operation complete - send result and output to client
op->complete(res, &st);
}
}
方法很長,我把重點挑出來分析。
首先我們看看調用AttachListener::pd_init()完了什麼:
int AttachListener::pd_init() {
JavaThread* thread = JavaThread::current();
ThreadBlockInVM tbivm(thread);
thread->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition() or
// java_suspend_self() via check_and_wait_while_suspended()
int ret_code = LinuxAttachListener::init();
// were we externally suspended while we were waiting?
thread->check_and_wait_while_suspended();
return ret_code;
}
int LinuxAttachListener::init() {
char path[UNIX_PATH_MAX]; // socket file
char initial_path[UNIX_PATH_MAX]; // socket file during setup
int listener; // listener socket (file descriptor)
// register function to cleanup
::atexit(listener_cleanup);
int n = snprintf(path, UNIX_PATH_MAX, "%s/.java_pid%d",
os::get_temp_directory(), os::current_process_id());
if (n < (int)UNIX_PATH_MAX) {
n = snprintf(initial_path, UNIX_PATH_MAX, "%s.tmp", path);
}
if (n >= (int)UNIX_PATH_MAX) {
return -1;
}
// create the listener socket
listener = ::socket(PF_UNIX, SOCK_STREAM, 0);//創建套接字
if (listener == -1) {
return -1;
}
// bind socket
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, initial_path);
::unlink(initial_path);
int res = ::bind(listener, (struct sockaddr*)&addr, sizeof(addr));//綁定地址
if (res == -1) {
::close(listener);
return -1;
}
// put in listen mode, set permissions, and rename into place
res = ::listen(listener, 5);//發起監聽
if (res == 0) {
RESTARTABLE(::chmod(initial_path, S_IREAD|S_IWRITE), res);
if (res == 0) {
res = ::rename(initial_path, path);
}
}
if (res == -1) {
::close(listener);
::unlink(initial_path);
return -1;
}
set_path(path);
set_listener(listener);
return 0;
}
不難發現,AttachListener::pd_init()方法又調用了LinuxAttachListener::init()方法,完成了對套接字的創建和監聽。這與jstack本地代碼建立socket連接發送命令,不謀而合。
再就是有一個for死循環,不停地調用AttachOperation* op = AttachListener::dequeue();獲取操作對象。如果進入到AttachListener::dequeue()方法看一看,其實就是在讀上邊監聽的套接字,我這裏就不貼源碼了。
在這個死循環裏,我們重點看看如下代碼:
// find the function to dispatch too
AttachOperationFunctionInfo* info = NULL;
for (int i=0; funcs[i].name != NULL; i++) {
const char* name = funcs[i].name;
assert(strlen(name) <= AttachOperation::name_length_max, "operation <= name_length_max");
if (strcmp(op->name(), name) == 0) {
info = &(funcs[i]);
break;
}
}
// check for platform dependent attach operation
if (info == NULL) {
info = AttachListener::pd_find_operation(op->name());
}
if (info != NULL) {
// dispatch to the function that implements this operation
res = (info->func)(op, &st);//調動方法
} else {
st.print("Operation %s not recognized!", op->name());
res = JNI_ERR;
}
}
// operation complete - send result and output to client
op->complete(res, &st);
這個for循環會遍歷funcs數組,然後根據從隊列裏拿到的AttachOperation對象的name來找到一個匹配的AttachOperationFunctionInfo對象,然後調用其func方法。
看到這裏你或許很多疑惑,當然看看funcs數組裏的東西,就開朗了:
static AttachOperationFunctionInfo funcs[] = {
{ "agentProperties", get_agent_properties },
{ "datadump", data_dump },
{ "dumpheap", dump_heap },
{ "load", JvmtiExport::load_agent_library },
{ "properties", get_system_properties },
{ "threaddump", thread_dump },
{ "inspectheap", heap_inspection },
{ "setflag", set_flag },
{ "printflag", print_flag },
{ "jcmd", jcmd },
{ NULL, NULL }
};
有沒有看到上文中我們提到的threaddump命令。jstack通過與jvm進程建立socket連接,然後向jvm進程發送threaddump指令。上文說道調用AttachOperationFunctionInfo對象的func方法處理指令,其實就是調用了thread_dump方法,針對threaddump命令來說。
堅持,馬上就要說完了。來看看thread_dump方法幹了些啥吧:
// Implementation of "threaddump" command - essentially a remote ctrl-break
// See also: ThreadDumpDCmd class
//
static jint thread_dump(AttachOperation* op, outputStream* out) {
bool print_concurrent_locks = false;
if (op->arg(0) != NULL && strcmp(op->arg(0), "-l") == 0) {
print_concurrent_locks = true;
}
// 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;
}
很簡單,創建了VM_PrintThreads、VM_PrintJNI、VM_FindDeadlocks三個對象,扔給了VM Thread線程的隊列。
說到這裏,VM Thread線程的作用,應該真相大白了,就是讀取隊列,然後執行相應的操作。有興趣你可以繼續追進去看看源代碼,我這裏就不追下去了。
總結
看了這麼多代碼,確實很頭疼,總結下吧。
jstack是通過與jvm進程建立socket連接,然後發送指令來實現相關操作。
jvm的Attach Listener線程監聽套接字,讀取jstack發來的指令,然後將相關的操作扔給VM Thread線程來執行,最後返回給jstack。
在jvm啓動的時候,如果沒有指定StartAttachListener,Attach Listener線程是不會啓動的,在Signal Dispatcher線程收到SIGBREAK信號時,會調用 AttachListener::is_init_trigger()通過調用用AttachListener::init()啓動了Attach Listener 線程。