在java中,如何通过访问内存拿到线程列表,用于跟踪线程的运行状态,这也是jstack的主要功能。 在jvm里,有没有F的参数实现笔者前面的博客已经说明了。因为-F是通过访问java的内存来取的信息的,所以当使用-F参数的时候,需要知道java运行过程中内存的结构,从而通过访问内存能获取到你所需要的信息。
1. 结构体 VMStructEntry 和 VMTypeEntry
typedef struct { const char* typeName; // The type name containing the given field (example: "Klass") const char* fieldName; // The field name within the type (example: "_name") const char* typeString; // Quoted name of the type of this field (example: "symbolOopDesc*"; // parsed in Java to ensure type correctness int32_t isStatic; // Indicates whether following field is an offset or an address uint64_t offset; // Offset of field within structure; only used for nonstatic fields void* address; // Address of field; only used for static fields // ("offset" can not be reused because of apparent SparcWorks compiler bug // in generation of initializer data) } VMStructEntry; typedef struct { const char* typeName; // Type name (example: "methodOopDesc") const char* superclassName; // Superclass name, or null if none (example: "oopDesc") int32_t isOopType; // Does this type represent an oop typedef? (i.e., "methodOop" or // "klassOop", but NOT "methodOopDesc") int32_t isIntegerType; // Does this type represent an integer type (of arbitrary size)? int32_t isUnsigned; // If so, is it unsigned? uint64_t size; // Size, in bytes, of the type } VMTypeEntry;因为在elf文件里并不会暴露类的名字,偏移,和继承关系,所以在jvm里单独定义了结构体用来保存类的关系,注意这里的c++的类,而不是java里的类.
eg.
class Threads: AllStatic { private: static JavaThread* _thread_list; static int _number_of_threads; static int _number_of_non_daemon_threads; static int _return_code; ... }VMTypeEntry 里面定义的是 typename="Threads", superclassName ="Null" ... size="sizeof(Threads)"
VMStructEntry 里面定义 typeName="Threads", fieldName ="_thread_list" typeString="JavaThread*" isstatic="1" offset="0" address="&Threads::_thread_list"
并且定义了参数
gHotSpotVMStructs VMStructEntry 数组
gHotSpotVMStructEntryTypeNameOffset VMTypeEntry 结构体中的参数typeName的偏移
gHotSpotVMStructEntryFieldNameOffset VMTypeEntry 结构体中的参数fieldName 的偏移
gHotSpotVMStructEntryTypeStringOffset VMTypeEntry 结构体中的参数typeString的偏移
gHotSpotVMStructEntryIsStaticOffset VMTypeEntry 结构体中的参数isstatic的偏移
gHotSpotVMStructEntryOffsetOffset VMTypeEntry 结构体中的参数offset的偏移
gHotSpotVMStructEntryAddressOffset VMTypeEntry 结构体中的参数address的偏移
gHotSpotVMStructEntryArrayStride VMTypeEntry 数组VMStructEntry之间地址的偏移
gHotSpotVMTypes VMTypeEntry 数组
gHotSpotVMTypeEntryTypeNameOffset ,
gHotSpotVMTypeEntrySuperclassNameOffset ,
gHotSpotVMTypeEntryIsOopTypeOffset ,
gHotSpotVMTypeEntryIsIntegerTypeOffset,
gHotSpotVMTypeEntryIsUnsignedOffset,
gHotSpotVMTypeEntrySizeOffset,gHotSpotVMTypeEntryArrayStride
2. 查找类的结构体
在jvm中定义的gHotSpotVMStructs ,和gHotSpotVMTypes是全局指针,可以直接通过访问符号表通过偏移计算得到真实的地址,请参考博客(Java 工具(jmap,jstack)在linux上的源码分析(六) -F 参数 读取动态链接共享库文件中的符号表)
然后通过上面一章节里提到的gHotSpotVM....参数获取到已经定义的类的名字,成员。
3. 如何查找线程列表
在thread.hpp里定义了在jvm里所定义的线程的类, 其中 Threads/ JavaThread 类如下
class Threads: AllStatic { friend class VMStructs; private: static JavaThread* _thread_list; static int _number_of_threads; static int _number_of_non_daemon_threads; static int _return_code; ... }这里有两个成员指针 一个是_thread_list,一个是 _next,在threads中的成员变量_thread_list 指针指向的是线程中的第一个线程,而javathread中的_next是指向下个javathread, 也就是javathread是链表结构,可以通过threads,thread_list 取的第一个线程,在通过_next取下一个线程,从而达到遍历整个线程列表的目地。
如何取的_thread_list?
查看宏定义VM_STRUCTS和VM_TYPES,可以看到已经将 _thread_list 和Threads加到VMStructEntry ,VMTypeEntry中去,因为对static 变量是保存地址的,所以可以通过访问参数gHotSpotVMStructs ,gHotSpotVMTypes 找到成员 _thread_list,成员_thread_list是一个指针,那么这个指针的值就是这个成员类的开始地址。
当取的第一个线程后,取的 JavaThread 的VMStructEntry 中的offset,通过地址+offset取的_next的指针的地址,以此类推,通过链表遍历所有线程,知道_next的地址为0为止。
4. 线程的类型
在jvm中定义了许多不同的内部的线程类型,都是通过继承javathread类来实现的,具体详细可参考在thread.hpp
当在遍历线程的列表,如何判断是什么线程的类型呢,对每个对象来说,指针的值就是对象的地址,读取这个地址的后8个byte(64位机器),所存放的值就是虚拟表中的标识这个类的地址。
这个虚拟表也在elf文件中,以_ZTV为标识, class CompilerThread 格式为 _ZTV14CompilerThread.
比较2者地址是否相等,就可以判断是否是这个类型的类。