首页 > 代码库 > 读书笔记之inside JVM(4)

读书笔记之inside JVM(4)

在我们常用的Jstack, Jmap 用于分析java虚拟机的状态的工具,通过起另一个虚拟机通过运行sun.tools包下的java文件,去跟踪另一个虚拟机的状态。


如果让你设计一个跟踪另一个进程的方法,你也通常会考虑这几种常用的方式。

第一种,就是通知被跟踪的进程,让进程执行相应的消息,同时对该消息做出反应。

第二种,就是通过内核的调用,直接能够访问进程的内存,堆栈情况,通过分析被跟踪的进程的内存结构,从而知道当前被跟踪的进程的状态。

第一种方式

优势:

对调用者和被调用者只要达成简单的通讯协议,调用者无需知道被调用者的逻辑,结构,只需要简单的发送命令的方式,被调用者能够接受到命令,并且对该命令进行回应就可以。

缺点:

如果被调用者当时的状态本来就不正常,或者繁忙,没办法对该命令做出响应,那这个跟踪进程往往是在规定的等待时间里,无法返回正确的需要的信息。其次被调用者在分析的过程中,有可能需要暂停进程中的其他的线程,而对被跟踪的进程有一定的影响。

第二种方式

优势:

通过内核的支持,访问被跟踪的内存,并作出快照,后台分析,很少影响被跟踪的进程。

缺点:

这种方式需要对被跟踪程的内存分配和使用非常的了解,无法解耦,而本身系统内核调用也会出问题。


Java工具类中也是大致实现了这2中方式,工具中会先选择第一种方式,如果发现第一种方式不能成功,将会建议使用-F参数,也就是第二种方式。

我们先讲第一种方式。

既然是需要向被跟踪进程发出命令,在linux中可以选择多种方式进行进程中通讯 共享内存,文件之类,其中创建socket的文件实现通讯是比较简单的方法。

下面是整个的流程图:

当java虚拟机启动的时候,会启动很多内部的线程,这些线程主要在thread.cpp里的create_vm方法体里实现

而在thread.cpp里主要起了2个线程来处理信号相关的

[cpp]   view plain copy
  1. JvmtiExport::enter_live_phase();  
  2.   
  3. // Signal Dispatcher needs to be started before VMInit event is posted  
  4. os::signal_init();  
  5.   
  6. // Start Attach Listener if +StartAttachListener or it can‘t be started lazily  
  7. if (!DisableAttachMechanism) {  
  8.   if (StartAttachListener || AttachListener::init_at_startup()) {  
  9.     AttachListener::init();  
  10.   }  
  11. }  


 

1. Signal Dispatcher 线程

在os.cpp中的signal_init()函数中,启动了signal dispatcher 线程,对signal dispather 线程主要是用于处理信号,等待信号并且分发处理,可以详细看signal_thread_entry的方法

[cpp]   view plain copy
  1. static void signal_thread_entry(JavaThread* thread, TRAPS) {  
  2.   os::set_priority(thread, NearMaxPriority);  
  3.   while (true) {  
  4.     int sig;  
  5.     {  
  6.       // FIXME : Currently we have not decieded what should be the status  
  7.       //         for this java thread blocked here. Once we decide about  
  8.       //         that we should fix this.  
  9.       sig = os::signal_wait();  
  10.     }  
  11.     if (sig == os::sigexitnum_pd()) {  
  12.        // Terminate the signal thread  
  13.        return;  
  14.     }  
  15.   
  16.     switch (sig) {  
  17.       case SIGBREAK: {  
  18.         // Check if the signal is a trigger to start the Attach Listener - in that  
  19.         // case don‘t print stack traces.  
  20.         if (!DisableAttachMechanism && AttachListener::is_init_trigger()) {  
  21.           continue;  
  22.         }  
  23.         // Print stack traces  
  24.         // Any SIGBREAK operations added here should make sure to flush  
  25.         // the output stream (e.g. tty->flush()) after output.  See 4803766.  
  26.         // Each module also prints an extra carriage return after its output.  
  27.         VM_PrintThreads op;  
  28.         VMThread::execute(&op);  
  29.         VM_PrintJNI jni_op;  
  30.         VMThread::execute(&jni_op);  
  31.         VM_FindDeadlocks op1(tty);  
  32.         VMThread::execute(&op1);  
  33.         Universe::print_heap_at_SIGBREAK();  
  34.         if (PrintClassHistogram) {  
  35.           VM_GC_HeapInspection op1(gclog_or_tty, true /* force full GC before heap inspection */,  
  36.                                    true /* need_prologue */);  
  37.           VMThread::execute(&op1);  
  38.         }  
  39.         if (JvmtiExport::should_post_data_dump()) {  
  40.           JvmtiExport::post_data_dump();  
  41.         }  
  42.         break;  
  43.       }  
  44.       default: {  
  45.         // Dispatch the signal to java  
  46.         HandleMark hm(THREAD);  
  47.         klassOop k = SystemDictionary::resolve_or_null(vmSymbolHandles::sun_misc_Signal(), THREAD);  
  48.         KlassHandle klass (THREAD, k);  
  49.         if (klass.not_null()) {  
  50.           JavaValue result(T_VOID);  
  51.           JavaCallArguments args;  
  52.           args.push_int(sig);  
  53.           JavaCalls::call_static(  
  54.             &result,  
  55.             klass,  
  56.             vmSymbolHandles::dispatch_name(),  
  57.             vmSymbolHandles::int_void_signature(),  
  58.             &args,  
  59.             THREAD  
  60.           );  
  61.         }  
  62.         if (HAS_PENDING_EXCEPTION) {  
  63.           // tty is initialized early so we don‘t expect it to be null, but  
  64.           // if it is we can‘t risk doing an initialization that might  
  65.           // trigger additional out-of-memory conditions  
  66.           if (tty != NULL) {  
  67.             char klass_name[256];  
  68.             char tmp_sig_name[16];  
  69.             const char* sig_name = "UNKNOWN";  
  70.             instanceKlass::cast(PENDING_EXCEPTION->klass())->  
  71.               name()->as_klass_external_name(klass_name, 256);  
  72.             if (os::exception_name(sig, tmp_sig_name, 16) != NULL)  
  73.               sig_name = tmp_sig_name;  
  74.             warning("Exception %s occurred dispatching signal %s to handler"  
  75.                     "- the VM may need to be forcibly terminated",  
  76.                     klass_name, sig_name );  
  77.           }  
  78.           CLEAR_PENDING_EXCEPTION;  
  79.         }  
  80.       }  
  81.     }  
  82.   }  
  83. }  


可以看到通过os::signal_wait();等待信号,而在linux里是通过sem_wait()来实现,接受到SIGBREAK(linux 中的QUIT)信号的时候(关于信号处理请参考笔者的另一篇博客:java 中关于信号的处理在linux下的实现),第一次通过调用 AttachListener::is_init_trigger()初始化attach listener线程,详细见2.Attach Listener 线程。

  1. 第一次收到信号,会开始初始化,当初始化成功,将会直接返回,而且不返回任何线程stack的信息(通过socket file的操作返回),并且第二次将不在需要初始化。如果初始化不成功,将直接在控制台的outputstream中打印线程栈信息。
  2. 第二次收到信号,如果已经初始化过,将直接在控制台中打印线程的栈信息。如果没有初始化,继续初始化,走和第一次相同的流程。

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 方法体中实现

[cpp]   view plain copy
  1. static void attach_listener_thread_entry(JavaThread* thread, TRAPS) {  
  2.   os::set_priority(thread, NearMaxPriority);  
  3.   
  4.   if (AttachListener::pd_init() != 0) {  
  5.     return;  
  6.   }  
  7.   AttachListener::set_initialized();  
  8.   
  9.   for (;;) {  
  10.     AttachOperation* op = AttachListener::dequeue();    
  11.      if (op == NULL) {  
  12.       return;   // dequeue failed or shutdown  
  13.     }  
  14.   
  15.     ResourceMark rm;  
  16.     bufferedStream st;  
  17.     jint res = JNI_OK;  
  18.   
  19.     // handle special detachall operation  
  20.     if (strcmp(op->name(), AttachOperation::detachall_operation_name()) == 0) {  
  21.       AttachListener::detachall();  
  22.     } else {  
  23.       // find the function to dispatch too  
  24.       AttachOperationFunctionInfo* info = NULL;  
  25.       for (int i=0; funcs[i].name != NULL; i++) {  
  26.         const char* name = funcs[i].name;  
  27.         assert(strlen(name) <= AttachOperation::name_length_max, "operation <= name_length_max");  
  28.         if (strcmp(op->name(), name) == 0) {  
  29.           info = &(funcs[i]);  
  30.           break;  
  31.         }  
  32.       }  
  33.   
  34.       // check for platform dependent attach operation  
  35.       if (info == NULL) {  
  36.         info = AttachListener::pd_find_operation(op->name());  
  37.       }  
  38.   
  39.       if (info != NULL) {  
  40.         // dispatch to the function that implements this operation  
  41.         res = (info->func)(op, &st);  
  42.       } else {  
  43.         st.print("Operation %s not recognized!", op->name());  
  44.         res = JNI_ERR;  
  45.       }  
  46.     }  
  47.   
  48.     // operation complete - send result and output to client  
  49.     op->complete(res, &st);  
  50.   }  
  51. }  

在AttachListener::dequeue(); 在liunx里的实现就是监听刚才创建的socket的文件,如果有请求进来,找到请求对应的操作,调用操作得到结果并把结果写到这个socket的文件,如果你把socket的文件删除,jstack/jmap会出现错误信息 unable to open socket file:........

 

我们经常使用 kill -3 pid的操作打印出线程栈信息,我们可以看到具体的实现是在Signal Dispatcher 线程中完成的,因为kill -3 pid 并不会创建.attach_pid#pid文件,所以一直初始化不成功,从而线程的栈信息被打印到控制台中。

信号转发线程,Attach Listener 线程都只是操作socket文件,并没有去执行比如stack 分析,或者heap的分析,真正的工作线程其实是vm thread.

(一)启动vm thread

[cpp]   view plain copy
  1. jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {  
  2. ...  
  3.   // Create the VMThread  
  4.   { TraceTime timer("Start VMThread", TraceStartupTime);  
  5.     VMThread::create();  
  6.     Thread* vmthread = VMThread::vm_thread();  
  7.   
  8.     if (!os::create_thread(vmthread, os::vm_thread))  
  9.       vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");  
  10.   
  11.     // Wait for the VM thread to become ready, and VMThread::run to initialize  
  12.     // Monitors can have spurious returns, must always check another state flag  
  13.     {  
  14.       MutexLocker ml(Notify_lock);  
  15.       os::start_thread(vmthread);  
  16.       while (vmthread->active_handles() == NULL) {  
  17.         Notify_lock->wait();  
  18.       }  
  19.     }  
  20.   }  
  21. ...  
  22.   
  23.   
  24. }  

我们可以看到,在thread.cpp里启动了线程vm thread,在这里我们同时也稍微的略带的讲一下jvm在linux里如何启动线程的。

通常在linux中启动线程,是调用

[cpp]   view plain copy
  1. int pthread_create((pthread_t *__thread, __const pthread_attr_t *__attr,void *(*__start_routine) (void *), void *__arg));  


而在java里却增加了os:create_thread --初始化线程 和os:start_thread--启动线程

我们去看一下jvm里面是如何在linux里做到的

在os_linux.cpp中来看create_thread的方法

[cpp]   view plain copy
  1. bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {  
  2. ....  
  3. int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);  
  4. ....  
  5. }  

继续看java_start方法

[cpp]   view plain copy
  1. static void *java_start(Thread *thread) {  
  2. ....  
  3.   // handshaking with parent thread  
  4.   {  
  5.     MutexLockerEx ml(sync, Mutex::_no_safepoint_check_flag);  
  6.   
  7.     // notify parent thread  
  8.     osthread->set_state(INITIALIZED);  
  9.     sync->notify_all();  
  10.   
  11.     // wait until os::start_thread()  
  12.     while (osthread->get_state() == INITIALIZED) {  
  13.       sync->wait(Mutex::_no_safepoint_check_flag);  
  14.     }  
  15.   }  
  16.   
  17.   // call one more level start routine  
  18.   thread->run();  
  19.   
  20.   return 0;  
  21. }  


首先jvm先设置了当前线程的状态是Initialized, 然后notify所有的线程,

 while (osthread->get_state() == INITIALIZED) {
      sync->wait(Mutex::_no_safepoint_check_flag);
    }

不停的查看线程的当前状态是不是Initialized, 如果是的话,调用了sync->wait()的方法等待。

来看os:start_thread的方法 os.cpp

[cpp]   view plain copy
  1. void os::start_thread(Thread* thread) {  
  2.   // guard suspend/resume  
  3.   MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);  
  4.   OSThread* osthread = thread->osthread();  
  5.   osthread->set_state(RUNNABLE);  
  6.   pd_start_thread(thread);  
  7. }  

这时候设置了线程的状态为runnable,但没有notify线程

在 pd_start_thread(thread)中, os_linux.cpp中

[cpp]   view plain copy
  1. void os::pd_start_thread(Thread* thread) {  
  2.   OSThread * osthread = thread->osthread();  
  3.   assert(osthread->get_state() != INITIALIZED, "just checking");  
  4.   Monitor* sync_with_child = osthread->startThread_lock();  
  5.   MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);  
  6.   sync_with_child->notify();  
  7. }  


这时候我们看到了notify 线程的操作

也就是这时候notify了线程,因为这时候的线程的状态是RUNNABLE, 方法java_start继续往下执行,于是调用了thread->run()的方法

 

对于线程vm Thread 也就是调用了vmthread::run方法

vmThread.cpp

[cpp]   view plain copy
  1. void VMThread::run() {  
  2. ...  
  3. this->loop();  
  4. ...  
  5. }  

调用了loop函数,处理了VM_Operation 的queue 关于queue的级别和优先级处理算法:可以参考 另一篇博客:http://blog.csdn.net/raintungli/article/details/6553337

 

(二)Jstack 运行在vm thread里的VM_Operation

jstack 处理也就是在前面博客所提到的attach Listener 线程所做的 operation

[cpp]   view plain copy
  1. static jint thread_dump(AttachOperation* op, outputStream* out) {  
  2.   bool print_concurrent_locks = false;  
  3.   if (op->arg(0) != NULL && strcmp(op->arg(0), "-l") == 0) {  
  4.     print_concurrent_locks = true;  
  5.   }  
  6.   
  7.   // thread stacks  
  8.   VM_PrintThreads op1(out, print_concurrent_locks);  
  9.   VMThread::execute(&op1);  
  10.   
  11.   // JNI global handles  
  12.   VM_PrintJNI op2(out);  
  13.   VMThread::execute(&op2);  
  14.   
  15.   // Deadlock detection  
  16.   VM_FindDeadlocks op3(out);  
  17.   VMThread::execute(&op3);  
  18.   
  19.   return JNI_OK;  
  20. }  

简单看一下类VM_PrintThreads 它 继承了VM_Operation

[cpp]   view plain copy
  1. class VM_PrintThreads: public VM_Operation {  
  2.  private:  
  3.   outputStream* _out;  
  4.   bool _print_concurrent_locks;  
  5.  public:  
  6.   VM_PrintThreads()                                                { _out = tty; _print_concurrent_locks = PrintConcurrentLocks; }  
  7.   VM_PrintThreads(outputStream* out, bool print_concurrent_locks)  { _out = out; _print_concurrent_locks = print_concurrent_locks; }  
  8.   VMOp_Type type() const                                           {  return VMOp_PrintThreads; }  
  9.   void doit();  
  10.   bool doit_prologue();  
  11.   void doit_epilogue();  
  12. };  

当调用VMThread::execute()也就是将VM_PrintThreads 放入了_vm_queue中,交给vm thread 处理,对vm thread来说取出queue里的VM_Operation,并且调用doit方法。

在jstack里,attach listener 的线程产生了VM_PrintThreads,VM_PrintJNI,VM_FindDeadlocks 3个operations,交给了vm thread  的线程处理。

 


读书笔记之inside JVM(4)