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

    xiaoxiao2023-12-28  155

    本文将主要介绍JVMTI的Heap系API,并利用这些API,实现一个类似 jmap -histo 的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 FlagsConstantValueDescriptionJVMTI_HEAP_FILTER_TAGGED0x4滤除已打标签的对象。JVMTI_HEAP_FILTER_UNTAGGED0x8滤除未打标签的对象。JVMTI_HEAP_FILTER_CLASS_TAGGED0x10滤除已打标签的class下的所有对象。JVMTI_HEAP_FILTER_CLASS_UNTAGGED0x20滤除未打标签的class下的所有对象。 如果这个入参为0,则不做任何过滤。 第二个入参klass作用是只传递所属指定入参class的对象给回调函数,其他对象一律滤除。如果这个入参为NULL,则不做任何限制。 注意:如果klass是接口,则不会有任何对象被传递给回调函数,klass的子类同样会被滤除。 第三个入参initial_obj,告诉函数该从哪个对象开始扫描引用。如果这个入参为NULL,则从Heap Roots开始扫描。Heap Roots一般为线程堆栈引用,系统class,JNI全局变量等。 第四个入参callbacks为回调函数。它是一个结构体,内部定义了多种类型的callbacks。 第五个入参user_data为外部传递给回调函数的数据。这里要注意,如果传递的是非指针类型的值,则可能在对象传递给回调函数时,发生一次值拷贝。如果每个对象的传递都需要值拷贝,将严重影响性能。解决办法就是定义方法体外全局变量。 **Heap Reference Callback** 这是FollowReference的主要回调函数。各个入参的含义见下表: ParametersNameTypeDescriptionreference_kind[jvmtiHeapReferenceKind](http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#jvmtiHeapReferenceKind)当前对象的类型。jvmtiHeapReferenceKind是一个枚举,请参见定义。reference_infoconst [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_datavoid*外部传递给回调函数的数据。详见上文的入参说明。 **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-3Author: 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 "

    相关资源:敏捷开发V1.0.pptx
    最新回复(0)