当前位置: 代码迷 >> 综合 >> Java 工具(jmap,jstack)在linux上的源码分析(七) -F 参数 如何读取线程列表
  详细解决方案

Java 工具(jmap,jstack)在linux上的源码分析(七) -F 参数 如何读取线程列表

热度:89   发布时间:2023-12-07 23:19:34.0

 

在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 correctnessint32_t  isStatic;               // Indicates whether following field is an offset or an addressuint64_t offset;                 // Offset of field within structure; only used for nonstatic fieldsvoid* 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者地址是否相等,就可以判断是否是这个类型的类。

 

 

 

 

  相关解决方案