溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何分析Java開(kāi)源工具在linux上的信號(hào)處理

發(fā)布時(shí)間:2021-10-29 10:49:02 來(lái)源:億速云 閱讀:196 作者:柒染 欄目:編程語(yǔ)言

今天就跟大家聊聊有關(guān)如何分析Java開(kāi)源工具在linux上的信號(hào)處理,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

當(dāng)java虛擬機(jī)啟動(dòng)的時(shí)候,會(huì)啟動(dòng)很多內(nèi)部的線程,這些線程主要在thread.cpp里的create_vm方法體里實(shí)現(xiàn)。

而在thread.cpp里主要起了2個(gè)線程來(lái)處理信號(hào)相關(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ù)中,啟動(dòng)了signal dispatcher 線程,對(duì)signal dispather 線程主要是用于處理信號(hào),等待信號(hào)并且分發(fā)處理,可以詳細(xì)看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;          }        }      }    }  }

可以看到通過(guò)os::signal_wait();等待信號(hào),而在linux里是通過(guò)sem_wait()來(lái)實(shí)現(xiàn),接受到SIGBREAK(linux 中的QUIT)信號(hào)的時(shí)候(關(guān)于信號(hào)處理請(qǐng)參考筆者的另一篇博客:java 中關(guān)于信號(hào)的處理在linux下的實(shí)現(xiàn)),***次通過(guò)調(diào)用 AttachListener::is_init_trigger()初始化attach listener線程,詳細(xì)見(jiàn)2.Attach Listener 線程。

第一次收到信號(hào),會(huì)開(kāi)始初始化,當(dāng)初始化成功,將會(huì)直接返回,而且不返回任何線程stack的信息(通過(guò)socket file的操作返回),并且第二次將不在需要初始化。如果初始化不成功,將直接在控制臺(tái)的outputstream中打印線程棧信息。
第二次收到信號(hào),如果已經(jīng)初始化過(guò),將直接在控制臺(tái)中打印線程的棧信息。如果沒(méi)有初始化,繼續(xù)初始化,走和***次相同的流程。

2. Attach Listener 線程

Attach Listener 線程是負(fù)責(zé)接收到外部的命令,而對(duì)該命令進(jìn)行執(zhí)行的并且吧結(jié)果返回給發(fā)送者。在jvm啟動(dòng)的時(shí)候,如果沒(méi)有指定+StartAttachListener,該線程是不會(huì)啟動(dòng)的,剛才我們討論到了在接受到quit信號(hào)之后,會(huì)調(diào)用 AttachListener::is_init_trigger()通過(guò)調(diào)用用AttachListener::init()啟動(dòng)了Attach Listener 線程,同時(shí)在不同的操作系統(tǒng)下初始化,在linux中 是在attachListener_Linux.cpp文件中實(shí)現(xiàn)的。

在linux中如果發(fā)現(xiàn)文件.attach_pid#pid存在,才會(huì)啟動(dòng)attach listener線程,同時(shí)初始化了socket 文件,也就是通常jmap,jstack tool干的事情,先創(chuàng)立attach_pid#pid文件,然后發(fā)quit信號(hào),通過(guò)這種方式暗式的啟動(dòng)了Attach Listener線程(見(jiàn)博客:http://blog.csdn.net/raintungli/article/details/7023092)。

線程的實(shí)現(xiàn)在 attach_listener_thread_entry 方法體中實(shí)現(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里的實(shí)現(xiàn)就是監(jiān)聽(tīng)剛才創(chuàng)建的socket的文件,如果有請(qǐng)求進(jìn)來(lái),找到請(qǐng)求對(duì)應(yīng)的操作,調(diào)用操作得到結(jié)果并把結(jié)果寫到這個(gè)socket的文件,如果你把socket的文件刪除,jstack/jmap會(huì)出現(xiàn)錯(cuò)誤信息 unable to open socket file:........

我們經(jīng)常使用 kill -3 pid的操作打印出線程棧信息,我們可以看到具體的實(shí)現(xiàn)是在Signal Dispatcher 線程中完成的,因?yàn)閗ill -3 pid 并不會(huì)創(chuàng)建.attach_pid#pid文件,所以一直初始化不成功,從而線程的棧信息被打印到控制臺(tái)中。

看完上述內(nèi)容,你們對(duì)如何分析Java開(kāi)源工具在linux上的信號(hào)處理有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI