Java开源工具在linux上的源码分析(详细说明配有图片).doc_第1页
Java开源工具在linux上的源码分析(详细说明配有图片).doc_第2页
Java开源工具在linux上的源码分析(详细说明配有图片).doc_第3页
Java开源工具在linux上的源码分析(详细说明配有图片).doc_第4页
Java开源工具在linux上的源码分析(详细说明配有图片).doc_第5页
已阅读5页,还剩20页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Java开源工具在linux上的源码分析(详细说明配有图片)目录索引Java开源工具在linux上的源码分析(一):跟踪方式2Java开源工具在linux上的源码分析(二):信号处理4Java开源工具在linux上的源码分析(三):执行的线程9Java开源工具在linux上的源码分析(四):safe point13Java开源工具在linux上的源码分析(五):-F参数的bug19Java开源工具在linux上的源码分析(六):符号表的读取22Java开源工具在linux上的源码分析(一):跟踪方式在我们常用的Jstack, Jmap 用于分析java虚拟机的状态的工具,通过起另一个虚拟机通过运行sun.tools包下的java文件,去跟踪另一个虚拟机的状态。AD: 在我们常用的Jstack, Jmap 用于分析java虚拟机的状态的工具,通过起另一个虚拟机通过运行sun.tools包下的java文件,去跟踪另一个虚拟机的状态。如果让你设计一个跟踪另一个进程的方法,你也通常会考虑这几种常用的方式。第一种,就是通知被跟踪的进程,让进程执行相应的消息,同时对该消息做出反应。第二种,就是通过内核的调用,直接能够访问进程的内存,堆栈情况,通过分析被跟踪的进程的内存结构,从而知道当前被跟踪的进程的状态。第一种方式优势:对调用者和被调用者只要达成简单的通讯协议,调用者无需知道被调用者的逻辑,结构,只需要简单的发送命令的方式,被调用者能够接受到命令,并且对该命令进行回应就可以。缺点:如果被调用者当时的状态本来就不正常,或者繁忙,没办法对该命令做出响应,那这个跟踪进程往往是在规定的等待时间里,无法返回正确的需要的信息。其次被调用者在分析的过程中,有可能需要暂停进程中的其他的线程,而对被跟踪的进程有一定的影响。第二种方式优势:通过内核的支持,访问被跟踪的内存,并作出快照,后台分析,很少影响被跟踪的进程。缺点:这种方式需要对被跟踪程的内存分配和使用非常的了解,无法解耦,而本身系统内核调用也会出问题。Java工具类中也是大致实现了这2中方式,工具中会先选择第一种方式,如果发现第一种方式不能成功,将会建议使用-F参数,也就是第二种方式。我们先讲第一种方式。既然是需要向被跟踪进程发出命令,在linux中可以选择多种方式进行进程中通讯 共享内存,文件之类,其中创建socket的文件实现通讯是比较简单的方法。下面是整个的流程图:原文链接:/raintungli/article/details/7023092Java开源工具在linux上的源码分析(二):信号处理当java虚拟机启动的时候,会启动很多内部的线程,这些线程主要在thread.cpp里的create_vm方法体里实现。而在thread.cpp里主要起了2个线程来处理信号相关的。详细请看下文AD: 当java虚拟机启动的时候,会启动很多内部的线程,这些线程主要在thread.cpp里的create_vm方法体里实现。而在thread.cpp里主要起了2个线程来处理信号相关的:1 JvmtiExport:enter_live_phase(); 23 / Signal Dispatcher needs to be started before VMInit event is posted 4 os:signal_init(); 56 / Start Attach Listener if +StartAttachListener or it cant 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的方法:12 static void signal_thread_entry(JavaThread* thread, TRAPS) 13 os:set_priority(thread, NearMaxPriority); 14 while (true) 15 int sig; 16 17 / FIXME : Currently we have not decieded what should be the status 18 / for this java thread blocked here. Once we decide about 19 / that we should fix this. 20 sig = os:signal_wait(); 21 22 if (sig = os:sigexitnum_pd() 23 / Terminate the signal thread 24 return; 25 2627 switch (sig) 28 case SIGBREAK: 29 / Check if the signal is a trigger to start the Attach Listener - in that 30 / case dont print stack traces. 31 if (!DisableAttachMechanism & AttachListener:is_init_trigger() 32 continue; 33 34 / Print stack traces 35 / Any SIGBREAK operations added here should make sure to flush 36 / the output stream (e.g. tty-flush() after output. See 4803766. 37 / Each module also prints an extra carriage return after its output. 38 VM_PrintThreads op; 39 VMThread:execute(&op); 40 VM_PrintJNI jni_op; 41 VMThread:execute(&jni_op); 42 VM_FindDeadlocks op1(tty); 43 VMThread:execute(&op1); 44 Universe:print_heap_at_SIGBREAK(); 45 if (PrintClassHistogram) 46 VM_GC_HeapInspection op1(gclog_or_tty, true /* force full GC before heap inspection */, 47 true /* need_prologue */); 48 VMThread:execute(&op1); 49 50 if (JvmtiExport:should_post_data_dump() 51 JvmtiExport:post_data_dump(); 52 53 break; 54 55 default: 56 / Dispatch the signal to java 57 HandleMark hm(THREAD); 58 klassOop k = SystemDictionary:resolve_or_null(vmSymbolHandles:sun_misc_Signal(), THREAD); 59 KlassHandle klass (THREAD, k); 60 if (klass.not_null() 61 JavaValue result(T_VOID); 62 JavaCallArguments args; 63 args.push_int(sig); 64 JavaCalls:call_static( 65 &result, 66 klass, 67 vmSymbolHandles:dispatch_name(), 68 vmSymbolHandles:int_void_signature(), 69 &args, 70 THREAD 71 ); 72 73 if (HAS_PENDING_EXCEPTION) 74 / tty is initialized early so we dont expect it to be null, but 75 / if it is we cant risk doing an initialization that might 76 / trigger additional out-of-memory conditions 77 if (tty != NULL) 78 char klass_name256; 79 char tmp_sig_name16; 80 const char* sig_name = UNKNOWN; 81 instanceKlass:cast(PENDING_EXCEPTION-klass()- 82 name()-as_klass_external_name(klass_name, 256); 83 if (os:exception_name(sig, tmp_sig_name, 16) != NULL) 84 sig_name = tmp_sig_name; 85 warning(Exception %s occurred dispatching signal %s to handler 86 - the VM may need to be forcibly terminated, 87 klass_name, sig_name ); 88 89 CLEAR_PENDING_EXCEPTION; 90 91 92 93 94 可以看到通过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线程(见博客:/raintungli/article/details/7023092)。线程的实现在 attach_listener_thread_entry 方法体中实现:95 static void attach_listener_thread_entry(JavaThread* thread, TRAPS) 96 os:set_priority(thread, NearMaxPriority); 9798 if (AttachListener:pd_init() != 0) 99 return; 100 101 AttachListener:set_initialized(); 102103 for (;) 104 AttachOperation* op = AttachListener:dequeue(); 105 if (op = NULL) 106 return; / dequeue failed or shutdown 107 108109 ResourceMark rm; 110 bufferedStream st; 111 jint res = JNI_OK; 112113 / handle special detachall operation 114 if (strcmp(op-name(), AttachOperation:detachall_operation_name() = 0) 115 AttachListener:detachall(); 116 else 117 / find the function to dispatch too 118 AttachOperationFunctionInfo* info = NULL; 119 for (int i=0; != NULL; i+) 120 const char* name = ; 121 assert(strlen(name) = AttachOperation:name_length_max, operation name(), name) = 0) 123 info = &(funcsi); 124 break; 125 126 127128 / check for platform dependent attach operation 129 if (info = NULL) 130 info = AttachListener:pd_find_operation(op-name(); 131 132133 if (info != NULL) 134 / dispatch to the function that implements this operation 135 res = (info-func)(op, &st); 136 else 137 st.print(Operation %s not recognized!, op-name(); 138 res = JNI_ERR; 139 140 141142 / operation complete - send result and output to client 143 op-complete(res, &st); 144 145 在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上的源码分析(三):执行的线程在前面的博客中所提到的信号转发线程,Attach Listener 线程都只是操作socket文件,并没有去执行比如stack 分析,或者heap的分析,真正的工作线程其实是vm thread.AD: 在前面的博客中(/raintungli/article/details/7034005)所提到的信号转发线程,Attach Listener 线程都只是操作socket文件,并没有去执行比如stack 分析,或者heap的分析,真正的工作线程其实是vm thread.(一)启动vm thread1 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(); 78 if (!os:create_thread(vmthread, os:vm_thread) 9 vm_exit_during_initialization(Cannot create VM thread. Out of system resources.); 1011 / 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 . 222324 我们可以看到,在thread.cpp里启动了线程vm thread,在这里我们同时也稍微的略带的讲一下jvm在linux里如何启动线程的。通常在linux中启动线程,是调用:25 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的方法:26 bool os:create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) 27 . 28 int ret = pthread_create(&tid, &attr, (void* (*)(void*) java_start, thread); 29 . 30 继续看java_start方法:31 static void *java_start(Thread *thread) 32 . 33 / handshaking with parent thread 34 35 MutexLockerEx ml(sync, Mutex:_no_safepoint_check_flag); 3637 / notify parent thread 38 osthread-set_state(INITIALIZED); 39 sync-notify_all(); 4041 / wait until os:start_thread() 42 while (osthread-get_state() = INITIALIZED) 43 sync-wait(Mutex:_no_safepoint_check_flag); 44 45 4647 / call one more level start routine 48 thread-run(); 4950 return 0; 51 首先jvm先设置了当前线程的状态是Initialized, 然后notify所有的线程,52 while (osthread-get_state() = INITIALIZED) 53 sync-wait(Mutex:_no_safepoint_check_flag); 54 不停的查看线程的当前状态是不是Initialized, 如果是的话,调用了sync-wait()的方法等待。来看os:start_thread的方法 os.cpp55 void os:start_thread(Thread* thread) 56 / guard suspend/resume 57 MutexLockerEx ml(thread-SR_lock(), Mutex:_no_safepoint_check_flag); 58 OSThread* osthread = thread-osthread(); 59 osthread-set_state(RUNNABLE); 60 pd_start_thread(thread); 61 这时候设置了线程的状态为runnable,但没有notify线程。在 pd_start_thread(thread)中, os_linux.cpp中:62 void os:pd_start_thread(Thread* thread) 63 OSThread * osthread = thread-osthread(); 64 assert(osthread-get_state() != INITIALIZED, just checking); 65 Monitor* sync_with_child = osthread-startThread_lock(); 66 MutexLockerEx ml(sync_with_child, Mutex:_no_safepoint_check_flag); 67 sync_with_child-notify(); 68 这时候我们看到了notify 线程的操作,也就是这时候notify了线程,因为这时候的线程的状态是RUNNABLE, 方法java_start继续往下执行,于是调用了thread-run()的方法。对于线程vm Thread 也就是调用了vmthread:run方法。vmThread.cpp69 void VMThread:run() 70 . 71 this-loop(); 72 . 73 调用了loop函数,处理了VM_Operation 的queue 关于queue的级别和优先级处理算法:可以参考 另一篇博客:/raintungli/article/details/6553337(二)Jstack 运行在vm thread里的VM_Operationjstack 处理也就是在前面博客所提到的attach Listener 线程所做的 operation74 static jint thread_dump(AttachOperation* op, outputStream* out) 75 bool print_concurrent_locks = false; 76 if (op-arg(0) != NULL & strcmp(op-arg(0), -l) = 0) 77 print_concurrent_locks = true; 78 7980 / thread stacks 81 VM_PrintThreads op1(out, print_concurrent_locks); 82 VMThread:execute(&op1); 8384 / JNI global handles 85 VM_PrintJNI op2(out); 86 VMThread:execute(&op2); 8788 / Deadlock detection 89 VM_FindDeadlocks op3(out); 90 VMThread:execute(&op3); 9192 return JNI_OK; 93 简单看一下类VM_PrintThreads 它 继承了VM_Operation94 class VM_PrintThreads: public VM_Operation 95 private: 96 outputStream* _out; 97 bool _print_concurrent_locks; 98 public: 99 VM_PrintThreads() _out = tty; _print_concurrent_locks = PrintConcurrentLocks; 100 VM_PrintThreads(outputStream* out, bool print_concurrent_locks) _out = out; _print_concurrent_locks = print_concurrent_locks; 101 VMOp_Type type() const return VMOp_PrintThreads; 102 void doit(); 103 bool doit_prologue(); 104 void doit_epilogue(); 105 ; 当调用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 的线程处理。Java开源工具在linux上的源码分析(四):safe pointsafe point 顾明思意,就是安全点,当需要jvm做一些操作的时候,需要把当前正在运行的线程进入一个安全点的状态(也可以说停止状态),这样才能做一些安全的操作,比如线程的dump,堆栈的信息。AD: safe point 顾明思意,就是安全点,当需要jvm做一些操作的时候,需要把当前正在运行的线程进入一个安全点的状态(也可以说停止状态),这样才能做一些安全的操作,比如线程的dump,堆栈的信息。在jvm里面通常vm_thread(我们一直在谈论的做一些属于vm 份内事情的线程) 和cms_thread(内存回收的线程)做的操作,是需要将其他的线程通过调用SafepointSynchronize:begin 和 SafepointSynchronize:end来实现让其他的线程进入或者退出safe point 的状态。通常safepoint 的有三种状态_not_synchronized说明没有任何打断现在所有线程运行的操作,也就是vm thread, cms thread 没有接到操作的指令_synchronizingvm thread,cms thread 接到操作指令,正在等待所有线程进入safe point_synchronized所有线程进入safe point, vm thread, cms thread 可以开始指令操作Java线程的状态通常在java 进程中的Java 的线程有几个不同的状态,如何让这些线程进入safepoint 的状态中,jvm是采用不同的方式a. 正在解释执行由于java是解释性语言,而线程在解释java 字节码的时候,需要dispatch table,记录方法地址进行跳转的,那么这样让线程进入停止状态就比较容易了,只要替换掉dispatch table 就可以了,让线程知道当前进入softpoint 状态。java里会设置3个DispatchTable, _active_table, _normal_table, _safept_table_active_table 正在解释运行的线程使用的dispatch table_normal_table 就是正常运行的初始化的dispatch table_safept_table safe point需要的dispatch table解释运行的线程一直都在使用_active_table,关键处就是在进入saftpoint 的时候,用_safept_table替换_active_table, 在退出saftpoint 的时候,使用_normal_table来替换_active_table。具体实现可以查看源码1 void TemplateInterpreter:notice_safepoints() 2 if (!_notice_safepoints) 3 / switch to safepoint dispatch table 4 _notice_safepoints = true; 5 copy_table(address*)&_safept_table, (address*)&_active_table, sizeof(_active_table) / sizeof(address); 6 7 89 / switch from the dispatch table which notices safepoints back to the 10 / normal dispatch table. So that we can notice single stepping points, 11 / keep the safepoint dispatch table if we are single stepping in JVMTI. 12 / Note that the should_post_single_step test is exactly as fast as the 13 / JvmtiExport:_enabled test and covers both cases. 14 void TemplateInterpreter:ignore_safepoints() 15 if (_notice_safepoints) 16 if (!JvmtiExport:should_post_single_step() 17 / switch to normal dispatch table 18 _notice_safepoints = false; 19 copy_table(address*)&_normal_table, (address*)&_active_table, sizeof(_active_table) / sizeof(address); 20 21 22 b. 运行在native code如果线程运行在native code的时候,vm thread 是不需要等待线程执行完的,只需要在从native code 返回的时候去判断一下 _state 的状态就可以了。在方法体里就是前面博客也出现过的 SafepointSynchronize:do_call_back()23 inline static bool do_call_back() 24 return (_state != _not_synchronized); 25 判断了_state 不是_not_synchronized状态为了能让线程从native code 回到java 的时候为了能读到/设置正确线程的状态,通常的解决方法使用memory barrier,java 使用OrderAccess:fence(); 在汇编里使用_asm_ volatile (lock; addl $0,0(%rsp) : : : cc, memory); 保证从内存里读到正确的值,但是这种方法严重影响系统的性能,于是java使用了每个线程都有独立的内存页来设置状态。通过使用使用参数-XX:+UseMembar 参数使用memory barrier,默认是不打开的,也就是使用独立的内存页来设置状态。c. 运行编译的代码1. Poling page 页面Poling page是在jvm初始化启动的时候会初始化的一个单独的内存页面,这个页面是让运行的编译过的代码的线程进入停止状态的关键。在linux里面使用了mmap初始化,源码如下26 address polling_page = (address) :mmap(NULL, Linux:page_size(), PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 2. 编译java 的JIT 会直接编译一些热门的源码到机器码,直接执行而不需要在解释执行从而提高效率,在编译的代码中,当函数或者方法块返回的时候会去访问一个内存poling页面。x86架构下27 void LIR_Assembler:return_op(LIR_Opr result) 28 assert(result-is_illegal() | !result-is_single_cpu() | result-as_register() = rax, word returns are in rax,); 29 if (!result-is_illegal() & result-is_float_kind() & !result-is_xmm_register() 30 assert(result-fpu() = 0, result must already be on TOS); 31 3233 / Pop the stack before the safepoint code 34 _ remove_frame(initial_frame_size_in_bytes(); 3536 bool result_is_oop = result-is_valid() ? result-is_oop() : false; 3738 / Note: we do not need to round double result; float result has the right precision 39 / the poll sets the condition code, but no data registers 40 AddressLiteral polling_page(os:get_polling_page() + (SafepointPollOffset % os:vm_page_size(), 41 relocInfo:poll_return_type); 4243 / NOTE: the requires that the polling page be reachable else the reloc 44 / goes to the movq that loads the address and not the faulting instruction 45 / which breaks the signal handler code 4647 _ test32(rax, polling_page); 4849 _ ret(0); 50 在前面提到的SafepointSynchronize:begin 函数源码中51 if (UseCompilerSafepoints & DeferPollingPageLoopCount si_addr) 63 stub = SharedRuntime:get_poll_stub(pc); 64 65 . 66 在linux x86,64 bit的体系中,poll stub 的地址 就是 SafepointSynchronize:handle_polling_page_exception 详细程序可见shareRuntime_x86_64.cpp回到safepoint.cpp中,SafepointSynchronize:handle_polling_page_exception通过取出线程的safepoint_stat,调用函数void ThreadSafepointState:handle_polling_page_exception,最后通过调用SafepointSynchronize:block(thread(); 来block当前线程。d. block 状态当线程进入block状态的时候,继续保持block状态。Java开源工具在linux上的源码分析(五):-F参数的bug当使用jmap,jstack是用-F参数的时候,是通过调用系统调用ptrace来取的寄存器的信息,在jdk6u23版本之前你会发现,当你使用jstack -F的时候 经常在logger 里面 看到错误信息,直接抛出异常,根本无法看到堆栈信息。AD: 当使用jmap,jstack是用-F参数的时候,是通过调用系统调用ptrace来取的寄存器的信息,关于linux下的ptrace实现可以参考我的博客(/raintungli/article/details/6563867)在jdk6u23版本之前你会发现,当你使用jstack -F的时候 经常在logger 里面 看到错误信息,直接抛出异常,根本无法看到堆栈信息。1 Thread 2

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论