Java開源工具在linux上的源碼分析(二):信號處理
當(dāng)java虛擬機啟動的時候,會啟動很多內(nèi)部的線程,這些線程主要在thread.cpp里的create_vm方法體里實現(xiàn)。
而在thread.cpp里主要起了2個線程來處理信號相關(guān)的:
- JvmtiExport::enter_live_phase();
- // Signal Dispatcher needs to be started before VMInit event is posted
- os::signal_init();
- // Start Attach Listener if +StartAttachListener or it can't be started lazily
- if (!DisableAttachMechanism) {
- if (StartAttachListener || AttachListener::init_at_startup()) {
- AttachListener::init();
- }
- }
1. Signal Dispatcher 線程
在os.cpp中的signal_init()函數(shù)中,啟動了signal dispatcher 線程,對signal dispather 線程主要是用于處理信號,等待信號并且分發(fā)處理,可以詳細看signal_thread_entry的方法:
- 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 */,
- true /* need_prologue */);
- VMThread::execute(&op1);
- }
- if (JvmtiExport::should_post_data_dump()) {
- JvmtiExport::post_data_dump();
- }
- break;
- }
- default: {
- // Dispatch the signal to java
- HandleMark hm(THREAD);
- klassOop k = SystemDictionary::resolve_or_null(vmSymbolHandles::sun_misc_Signal(), THREAD);
- KlassHandle klass (THREAD, k);
- if (klass.not_null()) {
- JavaValue result(T_VOID);
- JavaCallArguments args;
- args.push_int(sig);
- JavaCalls::call_static(
- &result,
- klass,
- vmSymbolHandles::dispatch_name(),
- vmSymbolHandles::int_void_signature(),
- &args,
- THREAD
- );
- }
- if (HAS_PENDING_EXCEPTION) {
- // tty is initialized early so we don't expect it to be null, but
- // if it is we can't risk doing an initialization that might
- // trigger additional out-of-memory conditions
- if (tty != NULL) {
- char klass_name[256];
- char tmp_sig_name[16];
- const char* sig_name = "UNKNOWN";
- instanceKlass::cast(PENDING_EXCEPTION->klass())->
- name()->as_klass_external_name(klass_name, 256);
- if (os::exception_name(sig, tmp_sig_name, 16) != NULL)
- sig_name = tmp_sig_name;
- warning("Exception %s occurred dispatching signal %s to handler"
- "- the VM may need to be forcibly terminated",
- klass_name, sig_name );
- }
- CLEAR_PENDING_EXCEPTION;
- }
- }
- }
- }
- }
可以看到通過os::signal_wait();等待信號,而在linux里是通過sem_wait()來實現(xiàn),接受到SIGBREAK(linux 中的QUIT)信號的時候(關(guān)于信號處理請參考筆者的另一篇博客:java 中關(guān)于信號的處理在linux下的實現(xiàn)),***次通過調(diào)用 AttachListener::is_init_trigger()初始化attach listener線程,詳細見2.Attach Listener 線程。
***次收到信號,會開始初始化,當(dāng)初始化成功,將會直接返回,而且不返回任何線程stack的信息(通過socket file的操作返回),并且第二次將不在需要初始化。如果初始化不成功,將直接在控制臺的outputstream中打印線程棧信息。
第二次收到信號,如果已經(jīng)初始化過,將直接在控制臺中打印線程的棧信息。如果沒有初始化,繼續(xù)初始化,走和***次相同的流程。
2. Attach Listener 線程
Attach Listener 線程是負責(zé)接收到外部的命令,而對該命令進行執(zhí)行的并且吧結(jié)果返回給發(fā)送者。在jvm啟動的時候,如果沒有指定+StartAttachListener,該線程是不會啟動的,剛才我們討論到了在接受到quit信號之后,會調(diào)用 AttachListener::is_init_trigger()通過調(diào)用用AttachListener::init()啟動了Attach Listener 線程,同時在不同的操作系統(tǒng)下初始化,在linux中 是在attachListener_Linux.cpp文件中實現(xiàn)的。
在linux中如果發(fā)現(xiàn)文件.attach_pid#pid存在,才會啟動attach listener線程,同時初始化了socket 文件,也就是通常jmap,jstack tool干的事情,先創(chuàng)立attach_pid#pid文件,然后發(fā)quit信號,通過這種方式暗式的啟動了Attach Listener線程(見博客:http://blog.csdn.net/raintungli/article/details/7023092)。
線程的實現(xiàn)在 attach_listener_thread_entry 方法體中實現(xiàn):
- static void attach_listener_thread_entry(JavaThread* thread, TRAPS) {
- os::set_priority(thread, NearMaxPriority);
- 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::dequeue(); 在liunx里的實現(xiàn)就是監(jiān)聽剛才創(chuàng)建的socket的文件,如果有請求進來,找到請求對應(yīng)的操作,調(diào)用操作得到結(jié)果并把結(jié)果寫到這個socket的文件,如果你把socket的文件刪除,jstack/jmap會出現(xiàn)錯誤信息 unable to open socket file:........
我們經(jīng)常使用 kill -3 pid的操作打印出線程棧信息,我們可以看到具體的實現(xiàn)是在Signal Dispatcher 線程中完成的,因為kill -3 pid 并不會創(chuàng)建.attach_pid#pid文件,所以一直初始化不成功,從而線程的棧信息被打印到控制臺中。
原文鏈接:http://blog.csdn.net/raintungli/article/details/7034005
【系列文章】