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 "

相关文章
|
5天前
Delphi调用BarTender打印条码标签简明开发教程
Delphi调用BarTender打印条码标签简明开发教程
|
14天前
|
Java
JavaFX库用于在Java中绘制移动平均线(MA)图表
JavaFX库用于在Java中绘制移动平均线(MA)图表。示例代码创建了一个LineChart,设置了坐标轴,添加了原始数据点,计算并显示了MA5线。通过调整参数和方法,可自定义图表外观。查看JavaFX文档以获取更多详细信息。
17 1
|
3月前
Google Earth Engine(GEE)——如何获取指定时间范围的影像值并进行图表展示(指定天数范围内的时序图)
Google Earth Engine(GEE)——如何获取指定时间范围的影像值并进行图表展示(指定天数范围内的时序图)
64 0
|
5月前
|
数据采集 数据挖掘 API
如何使用item_get接口获取不同类别的商品信息?
在电商行业中,商品类别是进行商品组织和管理的关键要素。通过商品类别,我们可以对商品进行分类、筛选和比较,以便更好地了解市场需求和消费者行为。本文将介绍如何使用item_get接口获取不同类别的商品信息,以及如何对这些信息进行数据分析和挖掘。
|
7月前
|
存储 Web App开发 开发者
SAP UI5 日期类型 sap.ui.model.type.Date 的解析格式问题分析
SAP UI5 日期类型 sap.ui.model.type.Date 的解析格式问题分析
38 0
|
8月前
|
Web App开发 JSON 前端开发
SAP UI5 应用开发教程之九十七 - 如何使用客户端 JSON 模型构建一个 Master-Detail-Detail 布局中的明细页面试读版
SAP UI5 应用开发教程之九十七 - 如何使用客户端 JSON 模型构建一个 Master-Detail-Detail 布局中的明细页面试读版
152 0
|
8月前
|
Web App开发 前端开发 JavaScript
SAP UI5 应用开发教程之九十二 - 基于 SAP UI5 JSONModel 客户端模型的列表分页显示(Table Pagination)前提试读版
SAP UI5 应用开发教程之九十二 - 基于 SAP UI5 JSONModel 客户端模型的列表分页显示(Table Pagination)前提试读版
32 0
|
8月前
|
Web App开发 JSON 前端开发
SAP UI5 应用开发教程之九十一 - 如何使用客户端 JSON 模型构建一个 Master-Detail-Detail 布局中的列表页面试读版
SAP UI5 应用开发教程之九十一 - 如何使用客户端 JSON 模型构建一个 Master-Detail-Detail 布局中的列表页面试读版
43 0
|
JavaScript 大数据
Qt+ECharts开发笔记(五):ECharts的动态排序柱状图介绍、基础使用和Qt封装Demo
上一篇的demo使用隐藏js代码的方式,实现了一个饼图的基本交互方式,并预留了Qt模块对外的基础接口。   本篇的demo实现了自动排序的柱状图,实现了一个自动排序柱状图的基本交互方式,即Qt调用js脚本操作html。   本篇demo使用Qt定时器方式,实现数据定时刷新自增,并预留出了定时器间隔参数。   像大数据网页常看的人口增长时间图,收入年度增长时间图等都是这一类。
Qt+ECharts开发笔记(五):ECharts的动态排序柱状图介绍、基础使用和Qt封装Demo
|
JavaScript
Qt+ECharts开发笔记(四):ECharts的饼图介绍、基础使用和Qt封装百分比图Demo
前一篇介绍了横向柱图图。本篇将介绍基础饼图使用,并将其封装一层Qt。本篇的demo使用隐藏js代码的方式,实现了一个饼图的基本交互方式,并预留了Qt模块对外的基础接口。
Qt+ECharts开发笔记(四):ECharts的饼图介绍、基础使用和Qt封装百分比图Demo