JVMTI开发教程之带引用关系的class柱状图

简介:

我们来看下上一节中的显示效果和本节例子的最终显示效果的异同:

Class统计信息柱状图Class统计信息柱状图
上图是上一节中例子中的显示效果。
带引用关系的Class统计信息柱状图
带引用关系的Class统计信息柱状图
上图是本节例子中的最终显示效果。可以看到,我们不仅可以获知某个class的实例数量,实例的总占用空间,以及class name。还能观察到class及其整棵引用树上的class的实例数量,空间,name等。

实现思路

本节没有用到任何新的JVMTI函数。不过比起第二节,会基于更多函数的入参(上一节中,许多入参都是闲置)来实现本例子中的功能。

上一节中,我们了解到,通过FollowReference的jvmtiHeapReferenceCallback可以得到JVMTI函数从Heap root开始扫描其下存活对象的引用关系树,这个过程非常类似GC。
为了实现本例的功能,我们需要利用这个函数,记录它回报的引用关系,并加工就能得到一颗完整的引用关系树。

typedef jint (JNICALL *jvmtiHeapReferenceCallback)
    (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);
回调函数中,我们需要关注的入参是 class_tag和referer_class_tag,后者代表引用当前对象的对象所属class的标签。通过这个标签,我们可以从ci_map中获取真实的ClassInfo. 具体实现详见下文的代码片段。 本例的难点是如何记录和构造这颗引用关系树。 笔者的实现方式比较简单,在ClassInfo结构中,维护一个单向链表。
class Referrer {
public:
    Referrer() {
        cls_id = 0;
        next = NULL;
    }
    ~Referrer() {
        cls_id = 0;
        delete next;
    }
    int cls_id;
    Referrer *next;
};

class ClassInfo {
public:
    ClassInfo() {
        name = NULL;
        cls_id = 0;
        instance_cnt = 0;
        instance_size = 0;
        cls_obj_flag = 0;
                referrer = NULL;
    }
    ~ClassInfo() {
        cls_id = 0;
        free(name);
        instance_cnt = 0;
        instance_size = 0;
        cls_obj_flag = 0;
                delete referrer;
    }
    int cls_id;
    char *name;
    int instance_cnt;
    long instance_size;
    int cls_obj_flag;
        Referrer *referrer; // 表示引用关系的单链表
};

在记录引用关系的同时,我们还需要注意:
1,递归引用需要排除。比如 A类成员变量也是A类自身。
2,java.lang.Class不需要计算到引用关系中。我们知道任何一个class都有引用它。忽略它可以提高存储和计算效率。
3,节点的祖先不应该出现重复的节点。
比如 A->B->C->A,这里第四层的A就不应该出现在引用关系树上。否则会重复打印(A后面又是 B->C->A->B->C->A…)。

实现代码片段:

if (referrer_class_tag > 0 && class_tag != referrer_class_tag) {
    Referrer *r = ci->referrer;
    if (NULL == r) {
        r = new Referrer();
        r->cls_id = referrer_class_tag;
        ci->referrer = r;
    } else if (r->cls_id != referrer_class_tag) {
        int type = 1;
        while (r->next != NULL) {
            if (r->next->cls_id == referrer_class_tag) {
                type = 0;
                break;
            }
            r = r->next;
        }
        if (type) {
            Referrer *r1 = new Referrer();
            r1->cls_id = referrer_class_tag;
            r->next = r1;
        }
    }
}

完整源码

这份源码与上一节中的源码非常类似,主要是 heapFRCallback 函数里添加了引用关系的记录。打印结果的程序片段处,添加递归打印树的函数。此外就是添加了新的数据结构 Referrer 。

下面是完整的源码,copy,另存为,编译即可运行。


/*

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

using namespace std;

class Referrer {
public:
Referrer() {
cls_id = 0;
next = NULL;
}
~Referrer() {
cls_id = 0;
delete next;
}
int cls_id;
Referrer *next;
};

class ClassInfo {
public:
ClassInfo() {
name = NULL;
cls_id = 0;
instance_cnt = 0;
instance_size = 0;
cls_obj_flag = 0;
referrer = NULL;
}
~ClassInfo() {
cls_id = 0;
free(name);
instance_cnt = 0;
instance_size = 0;
cls_obj_flag = 0;
delete referrer;
}
int cls_id;
char name;
int instance_cnt;
long instance_size;
int cls_obj_flag;
Referrer 
referrer;
};

ClassInfo *ci_map;
jvmtiEnv 
jvmti;
int seq;
int total_cls_size;
int depth = 5;

/**

  • 解析class符号,抽取出class name并格式化。
  • @return class name
    /
    char
     getClassName(jclass cls) {
    int xl = 0;
    char sig;
    char 
    data;
    jvmti->GetClassSignature(cls, &sig, NULL);
    if (sig) {
    xl = strlen(sig);
    data = (char*) malloc(256);
    if (xl > 1 && sig[xl - 1] == ';')
        xl--;
    int arrlen = 0;
    while (sig[arrlen] == '[')
        arrlen++;
    switch (sig[arrlen]) {
    case 'Z':
        strcpy(data, "boolean");
        xl = 7;
        break;
    case 'B':
        strcpy(data, "byte");
        xl = 4;
        break;
    case 'C':
        strcpy(data, "char");
        xl = 4;
        break;
    case 'S':
        strcpy(data, "short");
        xl = 5;
        break;
    case 'I':
        strcpy(data, "int");
        xl = 3;
        break;
    case 'J':
        strcpy(data, "long");
        xl = 4;
        break;
    case 'F':
        strcpy(data, "float");
        xl = 5;
        break;
    case 'D':
        strcpy(data, "double");
        xl = 6;
        break;
    case 'L': {
        strncpy(data, sig + arrlen + 1, xl - arrlen - 1);
        xl = xl - arrlen - 1;
        break;
    }
    default: {
        strncpy(data, sig + arrlen, xl - arrlen);
        xl = xl - arrlen;
        break;
    }
    }
    while (arrlen--) {
        data[xl++] = '[';
        data[xl++] = ']';
    }
    data[xl] = '';
    jvmti->Deallocate((unsigned char*) sig);
    char *tmp = data;
    while (*tmp) {
        if (*tmp == '/') {
            *tmp = '.';
        }
        tmp++;
    }
    
    }
    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;
}
}

// build reference tree
if (act_obj) {
    ClassInfo *ci = ci_map[class_tag];
    ci->instance_cnt++;
    ci->instance_size += size;

    /*
     * 记录引用关系.
     * 1,递归引用需要排除。比如 A类成员变量也是A类自身。
     * 2,java.lang.Class不需要计算到引用关系中。我们知道任何一个class都有引用它。
     * 忽略它可以提高存储和计算效率。
     * 3,节点的祖先不应该出现重复的节点。
     * 比如 A->B->C->A,这里第四层的A就不应该出现在引用关系树上。
     * 否则会重复打印(A后面又是 B->C->A->B->C->A...)。
     */
    if (referrer_class_tag > 0 && class_tag != referrer_class_tag) {
        Referrer *r = ci->referrer;
        if (NULL == r) {
            r = new Referrer();
            r->cls_id = referrer_class_tag;
            ci->referrer = r;
        } else if (r->cls_id != referrer_class_tag) {
            int type = 1;
            while (r->next != NULL) {
                if (r->next->cls_id == referrer_class_tag) {
                    type = 0;
                    break;
                }
                r = r->next;
            }
            if (type) {
                Referrer *r1 = new Referrer();
                r1->cls_id = referrer_class_tag;
                r->next = r1;
            }
        }
    }
}

return JVMTI_VISIT_OBJECTS;

}

jint JNICALL untagCallback(jlong class_tag, jlong size, jlong tag_ptr,
jint length, void
 user_data) {
*tag_ptr = 0;
return JVMTI_VISIT_OBJECTS;
}

Referrer get_max(Referrer head) {
Referrer *max = head;
while (NULL != head) {
if (ci_map[head->cls_id]->instance_size
> ci_map[max->cls_id]->instance_size) {
max = head;
}
head = head->next;
}
return max;
}

void sort_referrers(Referrer head) {
Referrer 
node = head, *tmp;
int value;
while (NULL != node) {
tmp = get_max(node);
if (node != tmp) {
value = node->cls_id;
node->cls_id = tmp->cls_id;
tmp->cls_id = value;
}
node = node->next;
}
}

bool allOnTree(Referrer *ci, set ref_tree) {
while (NULL != ci) {
if (ref_tree.find(ci->cls_id) == ref_tree.end()) {
return false;
}
ci = ci->next;
}
return true;
}

void printRefInfo(ClassInfo ci, int level, set ref_tree,
set grade_format) {
if (++level >= depth)
return;
ref_tree.insert(ci->cls_id);
Referrer 
referrer = ci->referrer;
sort_referrers(referrer);
int max = 0;
while (referrer != NULL) {
ClassInfo *c1 = ci_map[referrer->cls_id];
if (max++ cls_id) == ref_tree.end()) {
string strbuf(“”);

        for (int i = 0; i = depth || NULL == c1->referrer || allOnTree(
                c1->referrer, ref_tree)) {
            strbuf.append("|--");
        } else {
            strbuf.append("+--");
        }
        string name(c1->name);
        name.insert(0, strbuf);
        printf("       %12d  %13ld  %-sn", c1->instance_cnt,
                c1->instance_size, name.c_str());

        if (referrer->next == NULL)
            grade_format.insert(level);
        printRefInfo(c1, level, ref_tree, grade_format);
    }
    referrer = referrer->next;
}

}

JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM jvm, char options,
void reserved) {
/*

 * 初始化JVMTI环境
 */
jint result = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1);
if (result != JNI_OK) {
    printf("ERROR: Unable to access JVMTI!n");
    return result;
}
jvmtiCapabilities capa;
memset(&capa, 0, sizeof(capa));
capa.can_tag_objects = 1; // 允许给object打标签
jvmti->AddCapabilities(&capa);

/**
 * 获取所有已装载的class,并填充ClassInfo容器ci_map,供后面的程序快速查找。
 */
jvmtiError err = (jvmtiError) 0;
jclass *classes;
jint count;

err = jvmti->GetLoadedClasses(&count, &classes);
if (err) {
    printf("ERROR: JVMTI GetLoadedClasses failed!n");
    return err;
}

ci_map = (ClassInfo**) malloc(sizeof(ClassInfo*) * (count + 1));
ci_map[0] = NULL; // 0表示java.lang.Class
for (int i = 0; i name = getClassName(classes[i]);
    ci->cls_id = i + 1;
    ci_map[i + 1] = ci;
    jvmti->SetTag(classes[i], i + 1);
}
seq = count + 2; // 因为上面的递归使用count+1,所以作为后续seq起始值,需要+2
total_cls_size = count;

/**
 * 遍历所有对象,通过ci_map查找ClassInfo,然后累加实例数量和实例占用大小。
 */
jvmtiHeapCallbacks heapCallbacks;
(void) memset(&heapCallbacks, 0, sizeof(heapCallbacks));
heapCallbacks.heap_reference_callback = &heapFRCallback;
err = jvmti->FollowReferences(0, NULL, NULL, &heapCallbacks, (void*) NULL);
if (err) {
    printf("%dn", err);
    return err;
}

// class id sort by instance size
int ncount = total_cls_size;
int cls_ids[ncount + 1];
cls_ids[0] = 0;
for (int i = 1; i cls_id;
}
int max = ncount + 1, tmp;
for (int i = 1; i < max; i++) {
    for (int j = 1; j instance_size
                instance_size) {
            tmp = cls_ids[j];
            cls_ids[j] = cls_ids[j + 1];
            cls_ids[j + 1] = tmp;
        }
    }
}

printf("%5s  %12s  %13s  %10sn", "num ", "#instances", "#bytes",
        "class name");
printf("----------------------------------------------n");
int n = 1;
for (int i = 1; i < ncount + 1; i++) {
    ClassInfo *c = ci_map[cls_ids[i]];
    set ref_tree;
    set grade_format;
    if (depth == i || i == total_cls_size)
        grade_format.insert(0);
    printf("%4d:  %12d  %13ld  %-sn", n++, c->instance_cnt,
            c->instance_size, c->name);
    printRefInfo(c, 0, ref_tree, grade_format);
    ref_tree.clear();
    grade_format.clear();
    if (depth == i)
        break;
}

/**
 * 清理内存:untag object, clear ci_map, dispose jvmti env
 */
(void) memset(&heapCallbacks, 0, sizeof(heapCallbacks));
heapCallbacks.heap_iteration_callback = &untagCallback;
err = jvmti->IterateThroughHeap(0, NULL, &heapCallbacks, (void*) NULL);
if (err)
    return err;
int len = sizeof(ci_map) / sizeof(ClassInfo*);
for (int i = 0; i DisposeEnvironment();
jvmti = NULL;
seq = 0;
return JNI_OK;

}

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) {
// nothing to do
}

本文来源于"阿里中间件团队播客",原文发表时间" "

相关文章
|
6月前
|
存储 XML 编译器
【Android 从入门到出门】第二章:使用声明式UI创建屏幕并探索组合原则
【Android 从入门到出门】第二章:使用声明式UI创建屏幕并探索组合原则
135 3
|
6月前
|
Java
【Java每日一题】— —第二十四题:编程定义一个长方形类Rectangle
【Java每日一题】— —第二十四题:编程定义一个长方形类Rectangle
|
图形学
[学习][笔记] qt5 从入门到入坟:<12>Graphics View Framework
[学习][笔记] qt5 从入门到入坟:<12>Graphics View Framework
|
编解码 C#
基于C#的ArcEngine二次开发教程(17):获取栅格属性的接口及代码实现
基于C#的ArcEngine二次开发教程(17):获取栅格属性的接口及代码实现
基于C#的ArcEngine二次开发教程(17):获取栅格属性的接口及代码实现
|
数据可视化 计算机视觉
qml开发笔记(二):可视化元素基类Item详解(上半场anchors等等)
qml开发笔记(二):可视化元素基类Item详解(上半场anchors等等)
qml开发笔记(二):可视化元素基类Item详解(上半场anchors等等)
|
数据可视化 定位技术 计算机视觉
qml开发笔记(三):可视化元素基类Item详解(下半场)
qml开发笔记(三):可视化元素基类Item详解(下半场)
qml开发笔记(三):可视化元素基类Item详解(下半场)
|
API vr&ar 图形学
【100个 Unity小知识点】☀️ | Unity中使用代码查询Draw call、Tris和Verts等信息
Unity 小科普 老规矩,先介绍一下 Unity 的科普小知识: Unity是 实时3D互动内容创作和运营平台 。 包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助 Unity 将创意变成现实。 Unity 平台提供一整套完善的软件解决方案,可用于创作、运营和变现任何实时互动的2D和3D内容,支持平台包括手机、平板电脑、PC、游戏主机、增强现实和虚拟现实设备。 也可以简单把 Unity 理解为一个游戏引擎,可以用来专业制作游戏!
【100个 Unity小知识点】☀️ | Unity中使用代码查询Draw call、Tris和Verts等信息
|
Java
编写Java程序,方法练习题__构建英雄类,定义一个int类型的变量output,表示英雄的血量
编写Java程序,方法练习题__构建英雄类,定义一个int类型的变量output,表示英雄的血量
271 0
|
iOS开发
iOS开发常用方法--坐标转换
iOS开发常用方法--坐标转换
|
C# .NET 开发框架
WPF笔记 ( xmlns引用,Resource、Binding 前/后台加载,重新绑定) 2013.6.7更新
原文:WPF笔记 ( xmlns引用,Resource、Binding 前/后台加载,重新绑定) 2013.6.7更新 1、xmlns Mapping URI的格式是 clr-namespace:[;assembly=] (1)如果自定义类和XAML处在同一个Assembly之中,只还需要提供clr-namespace值。
1452 0