本文共 6587 字,大约阅读时间需要 21 分钟。
本文将主要介绍JVMTI的Heap系API,并利用这些API,实现一个类似 jmap -histo 的Class统计信息柱状图。
在上图中,我们可以获知某个class的实例数量,实例的总占用空间,以及class name。
注意:下文中提及的函数定义,均以C++版作为参照。
Get/SetTag
函数定义如下: 标签用于与某一个对象建立关联。之后,可通过标签来查找对象。 SetTag为对象添加标签,GetTag获取对象上的标签。 标签在Heap系API中得到了广泛使用。 **FollowReferences** 函数定义如下 这个函数的作用是,从roots或initial_obj开始扫描所有可达(有引用)的对象,按扫描顺序将这些对象做为入参回调给 jvmtiHeapCallbacks 里定义的所有回调函数。 函数本身提供了5个入参。 第一个入参heap_filter是过滤传递给回调函数的对象的标记。JVMTI定义了4个常量来表示4种不同的过滤规则,如下表所示(标签概念,详见Get/SetTag函数的解释):Heap Filter Flags | ||
Constant | Value | Description |
JVMTI_HEAP_FILTER_TAGGED | 0x4 | 滤除已打标签的对象。 |
JVMTI_HEAP_FILTER_UNTAGGED | 0x8 | 滤除未打标签的对象。 |
JVMTI_HEAP_FILTER_CLASS_TAGGED | 0x10 | 滤除已打标签的class下的所有对象。 |
JVMTI_HEAP_FILTER_CLASS_UNTAGGED | 0x20 | 滤除未打标签的class下的所有对象。 |
Parameters | ||
Name | Type | Description |
reference_kind | [jvmtiHeapReferenceKind](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#jvmtiHeapReferenceKind) | 当前对象的类型。jvmtiHeapReferenceKind是一个枚举,请参见定义。 |
reference_info | const [jvmtiHeapReferenceInfo](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#jvmtiHeapReferenceInfo) * | 引用的详细信息。 当 [reference_kind](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#.reference_kind) 是 [JVMTI_HEAP_REFERENCE_FIELD](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#JVMTI_HEAP_REFERENCE_FIELD), [JVMTI_HEAP_REFERENCE_STATIC_FIELD](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#JVMTI_HEAP_REFERENCE_STATIC_FIELD), [JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT), [JVMTI_HEAP_REFERENCE_CONSTANT_POOL](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#JVMTI_HEAP_REFERENCE_CONSTANT_POOL), [JVMTI_HEAP_REFERENCE_STACK_LOCAL](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#JVMTI_HEAP_REFERENCE_STACK_LOCAL), or [JVMTI_HEAP_REFERENCE_JNI_LOCAL](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#JVMTI_HEAP_REFERENCE_JNI_LOCAL). 时可以设置它,否则设 NULL. |
class_tag | [jlong](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#jlong) | 当前对象所属class的标签值(如果是0,则未打标签) 如果当前对象是一个运行时的class,则class_tag的值为Java.lang.Class的标签值(如果java.lang.Class未打标签,则为0)。 |
referrer_class_tag | [jlong](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#jlong) | 引用当前对象的对象所属class的标签值(如果是0,则未打标签或者引用当前对象的是heap root) 如果引用当前对象的对象是一个运行时的class,则 referrer_class_tag 的值为Java.lang.Class的标签值(如果java.lang.Class未打标签,则为0)。 |
size | [jlong](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#jlong) | 当前对象的大小(单位bytes) 同 [GetObjectSize](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#GetObjectSize). |
tag_ptr | [jlong](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#jlong)* | 当前对象的标签值,注意,这不同于对象所属class的标签值,除非当前对象就是java.lang.Class。如果标签值为0,说明当前对象未打标签。 在回调函数中,你可以为这个值赋值。这个操作类似调用了 SetTag。 |
referrer_tag_ptr | [jlong](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#jlong)* | 引用当前对象的对象标签值。如果为0,说明未打标签。如果是NULL,说明引用当前对象的对象是来自heap root。 在回调函数中,你可以为这个值赋值。这个操作类似调用了 SetTag。 如果回调函数的入参 referrer_tag_ptr == tag_ptr,则说明他们是一个对象内的递归引用。比如A内部的成员变量仍旧是A。 |
length | [jint](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#jint) | 如果这个对象是一个数组,则这个入参表示数组的长度。如果不是的话,它的值为-1. |
user_data | void* | 外部传递给回调函数的数据。详见上文的入参说明。 |
DisposeEnvironment
函数定义如下:在上文中,已经给出了本文实例的最终显示效果。
为了实现这个Class统计信息柱状图,我们来分析一下:
1,首先,我们需要得到所有已经装载的Class及相关信息,比如class name。 2,然后,需要定义一个c++ class或结构体,用以存储class name, 实例数量,实例占用空间这三个属性,和一个全局的队列,存储这些数据结构。 3,使用FollowReference函数,遍历整个从Heap root可达的对象树(假设先打印live对象)。 从上文jvmtiHeapReferenceCallback函数中了解到,该函数提供了对象的实例数量,加上FollowReference的对象遍历特性。我们就可以累加class的实例数量和总占用空间。在遍历过程中,如何将对象和我们的自定义class数据结构关联起来呢?
这里我们就需要用到 Tag 功能。大致思路是这样的: 在第一步得到所有已装载Class对象后,需要为每个Class对象打标签。标签值,就用遍历class时的序号1-n来表示吧。 在jvmtiHeapReferenceCallback回调函数中,我们利用入参class_tag(因为上一步我们为所有class打了标签,所以这就是当前对象所属class的标签值) 在全局队列中找到对应标签值的class自定数据结构,将对象和自定class数据结构最终关联起来。4,最后,按实例占用空间排序class自定义数据结构队列,遍历并打印最终显示效果。
5,清理内存,销毁相关资源。下面让我们来看源码吧!
class ClassInfo {
public: ClassInfo() { name = NULL; cls_id = 0; instance_cnt = 0; instance_size = 0; cls_obj_flag = 0; } ~ClassInfo() { cls_id = 0; free(name); instance_cnt = 0; instance_size = 0; cls_obj_flag = 0; } int cls_id; char *name; int instance_cnt; long instance_size; int cls_obj_flag; };ClassInfo *ci_map; jvmtiEnv jvmti;
int seq; int total_cls_size;/**
jint JNICALL heapFRCallback(jvmtiHeapReferenceKind reference_kind,
const jvmtiHeapReferenceInfo reference_info, jlong class_tag, jlong referrer_class_tag, jlong size, jlong tag_ptr, jlong referrer_tag_ptr, jint length, void user_data) { // clean duplicate int act_obj = 0; if (tag_ptr == 0) { tag_ptr = ++seq; act_obj = 1; } else if (*tag_ptr cls_obj_flag == 0) { ci->cls_obj_flag = 1; act_obj = 1; } }}
jint JNICALL untagCallback(jlong class_tag, jlong size, jlong tag_ptr, jint length, void user_data) {
*tag_ptr = 0; return JVMTI_VISIT_OBJECTS; }JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM jvm, char options,
void reserved) { /*}
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) {
// nothing to do }本文来源于"阿里中间件团队播客",原文发表时间" 2011-03-17 "
转载地址:http://aycda.baihongyu.com/