JVMTI开发教程之Class统计信息柱状图-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

JVMTI开发教程之Class统计信息柱状图

简介:

本文将主要介绍JVMTI的Heap系API,并利用这些API,实现一个类似 jmap -histo 的Class统计信息柱状图。

Class统计信息柱状图Class统计信息柱状图
在上图中,我们可以获知某个class的实例数量,实例的总占用空间,以及class name。

所用到的JVMTI Heap系API介绍

注意:下文中提及的函数定义,均以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下的所有对象。
如果这个入参为0,则不做任何过滤。 第二个入参klass作用是只传递所属指定入参class的对象给回调函数,其他对象一律滤除。如果这个入参为NULL,则不做任何限制。 注意:如果klass是接口,则不会有任何对象被传递给回调函数,klass的子类同样会被滤除。 第三个入参initial_obj,告诉函数该从哪个对象开始扫描引用。如果这个入参为NULL,则从Heap Roots开始扫描。Heap Roots一般为线程堆栈引用,系统class,JNI全局变量等。 第四个入参callbacks为回调函数。它是一个结构体,内部定义了多种类型的callbacks。 第五个入参user_data为外部传递给回调函数的数据。这里要注意,如果传递的是非指针类型的值,则可能在对象传递给回调函数时,发生一次值拷贝。如果每个对象的传递都需要值拷贝,将严重影响性能。解决办法就是定义方法体外全局变量。 **Heap Reference Callback**

这是FollowReference的主要回调函数。各个入参的含义见下表:
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* 外部传递给回调函数的数据。详见上文的入参说明。
**IterateThroughHeap** 函数定义如下:

这个函数的入参和功能非常类似FollowReferences,不同之点在于,它不从Heap roots或initial_obj开始扫描,它扫描了整个Heap里所有还未被GC的对象。换句话讲就是遍历了整个堆。 这个函数对应的主要回调函数是 [jvmtiHeapIterationCallback](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#jvmtiHeapIterationCallback)。因为该回调函数的定义与FollowReference如出一辙,所以本文不再赘述,请自行参照官方文档和FollowReference的入参含义表格。 **Deallocate** 函数定义如下:

如果有jvmti函数内部分配了内存的字串交由agent使用,用完后需要agent手动调用jvmti的Deallocate释放,否则会造成Memory Leak。 **AddCapabilities** 函数定义如下:

jvmti的总控配置。提供了jvmti大部分功能的开关。它一般作为初始化完jvmti环境后,第一个被调用的函数。如果不设置capa,则某些函数功能将失效。 本文中用到 FollowReference,就需要在capa中开启 can_tag_objects = 1. 下面是一个使用例子:

DisposeEnvironment
函数定义如下:



关闭JVMTI连接,销毁jvmti上下文的所有资源。

Class统计信息柱状图开发实例

在上文中,已经给出了本文实例的最终显示效果。

为了实现这个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,清理内存,销毁相关资源。

下面让我们来看源码吧!


  • JVMTI Tutorial - jmap -histo:live
    *
  • Created on: 2011-3-3
  • Author: kenwu
    */
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include

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;

/**

  • 解析class符号,抽取出class name并格式化。
  • @return class name
    /
    char
     getClassName(jclass cls) {
    int xl = 0;
    char sig;
    char 
    data;
    jvmti->GetClassSignature(cls, &sig, NULL);
    if (sig) {
    }
    return data;
    }

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 "

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章
最新文章
相关文章