在聊Jstack得工做原理前呢,不如讓咱們先寫一個簡單的jstack玩玩。不用怕,很簡單的,就幾行代碼的事,看:php
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進程號哦。java
jstack有兩種實現方式,一種是基於attach api,其實現能夠在tools.jar裏找到;另外一種是基於SA的實現,它被放在了sa-jdi.jar裏。若是你經過idea搜索Jstack類,你會看到tools.jar和sa-jdi.jar各有一個Jstack類。api
本文呢,就經過分析attch api的源碼,來了解jstack的工做原理。數組
咱們來看一下HotSpotVirtualMachine的remoteDataDump方法:jvm
public InputStream remoteDataDump(Object... var1) throws IOException { return this.executeCommand("threaddump", var1); }
他是在執行一個叫threaddump的命令。沿着這個executeCommand方法繼續往裏追,會發現他是調用了以下方法:socket
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進程。ide
注意:限於篇幅我並無貼整個方法代碼。execute是HotSpotVirtualMachine的抽象方法,不一樣平臺的jdk有不一樣的execute方法的實現,我這裏的代碼是mac下的execute實現,位於BsdVirtualMachine類中。oop
經過jtack本地源代碼,咱們大體能夠粗略的認爲:jstack就是經過與指定的jvm進程創建socket鏈接,而後發送指令,最後將jvm進程返回的內容打印出來。post
瞭解了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 線程。
加入知識星球,能夠有更多的交流,更多的學習和更快的提升。