Java 工具(jmap,jstack)在linux上的源码分析(六) -F 参数 如何

在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"

在vmStructs.cpp中宏定义了VM_STRUCTS和VM_TYPES, 在方法里指定了需要记录的类和变量集合并且定义了参数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;...}

class JavaThread: public Thread {  friend class VMStructs; private:  JavaThread*    _next;  ..}

这里有两个成员指针 一个是_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者地址是否相等,就可以判断是否是这个类型的类。

有时我们选择改变,并非经过深思熟虑,而更像是听见了天地间冥冥中的呼唤,

Java 工具(jmap,jstack)在linux上的源码分析(六) -F 参数 如何

相关文章:

你感兴趣的文章:

标签云: