天天看點

JVMTI開發教程之Class統計資訊柱狀圖

本文将主要介紹jvmti的heap系api,并利用這些api,實作一個類似 jmap -histo 的class統計資訊柱狀圖。

JVMTI開發教程之Class統計資訊柱狀圖

在上圖中,我們可以獲知某個class的執行個體數量,執行個體的總占用空間,以及class name。

注意:下文中提及的函數定義,均以c++版作為參照。

get/settag

函數定義如下:

這個函數的作用是,從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

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

引用目前對象的對象所屬class的标簽值(如果是0,則未打标簽或者引用目前對象的是heap root) 如果引用目前對象的對象是一個運作時的class,則 referrer_class_tag 的值為java.lang.class的标簽值(如果java.lang.class未打标簽,則為0)。

size

目前對象的大小(機關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

引用目前對象的對象标簽值。如果為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統計資訊柱狀圖,我們來分析一下:

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

class classinfo {

public:

classinfo() {

name = null;

cls_id = 0;

instance_cnt = 0;

instance_size = 0;

cls_obj_flag = 0;

}

~classinfo() {

free(name);

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;

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 "