今天就跟大家聊聊有关如何分析Java开源工具在linux上的信号处理,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
当java虚拟机启动的时候,会启动很多内部的线程,这些线程主要在thread.cpp里的create_vm方法体里实现。
而在thread.cpp里主要起了2个线程来处理信号相关的:
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()函数中,启动了signal dispatcher 线程,对signal dispather 线程主要是用于处理信号,等待信号并且分发处理,可以详细看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()来实现,接受到SIGBREAK(linux 中的QUIT)信号的时候(关于信号处理请参考笔者的另一篇博客:java 中关于信号的处理在linux下的实现),***次通过调用 AttachListener::is_init_trigger()初始化attach listener线程,详细见2.Attach Listener 线程。
第一次收到信号,会开始初始化,当初始化成功,将会直接返回,而且不返回任何线程stack的信息(通过socket file的操作返回),并且第二次将不在需要初始化。如果初始化不成功,将直接在控制台的outputstream中打印线程栈信息。
第二次收到信号,如果已经初始化过,将直接在控制台中打印线程的栈信息。如果没有初始化,继续初始化,走和***次相同的流程。
2. Attach Listener 线程
Attach Listener 线程是负责接收到外部的命令,而对该命令进行执行的并且吧结果返回给发送者。在jvm启动的时候,如果没有指定+StartAttachListener,该线程是不会启动的,刚才我们讨论到了在接受到quit信号之后,会调用 AttachListener::is_init_trigger()通过调用用AttachListener::init()启动了Attach Listener 线程,同时在不同的操作系统下初始化,在linux中 是在attachListener_Linux.cpp文件中实现的。
在linux中如果发现文件.attach_pid#pid存在,才会启动attach listener线程,同时初始化了socket 文件,也就是通常jmap,jstack tool干的事情,先创立attach_pid#pid文件,然后发quit信号,通过这种方式暗式的启动了Attach Listener线程(见博客:http://blog.csdn.net/raintungli/article/details/7023092)。
线程的实现在 attach_listener_thread_entry 方法体中实现:
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里的实现就是监听刚才创建的socket的文件,如果有请求进来,找到请求对应的操作,调用操作得到结果并把结果写到这个socket的文件,如果你把socket的文件删除,jstack/jmap会出现错误信息 unable to open socket file:........
我们经常使用 kill -3 pid的操作打印出线程栈信息,我们可以看到具体的实现是在Signal Dispatcher 线程中完成的,因为kill -3 pid 并不会创建.attach_pid#pid文件,所以一直初始化不成功,从而线程的栈信息被打印到控制台中。
看完上述内容,你们对如何分析Java开源工具在linux上的信号处理有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。