diff --git a/arthas-vmtool/pom.xml b/arthas-vmtool/pom.xml
index 653a58287db..5a5545a401f 100644
--- a/arthas-vmtool/pom.xml
+++ b/arthas-vmtool/pom.xml
@@ -26,6 +26,7 @@
macos
-m64
+ g++-11
libArthasJniLibrary-x64.dylib
@@ -42,6 +43,7 @@
linux
-m64
+ g++
libArthasJniLibrary-x64.so
@@ -58,6 +60,7 @@
windows
-m64
+ g++
libArthasJniLibrary-x64.dll
@@ -80,21 +83,23 @@
src/main/native/src
jni-library.cpp
+ HeapAnalyzer.cpp
generic-classic
- g++
+ ${os_arch_compiler}
${os_arch_option}
-fpic
-shared
+ -std=c++17
-o
target
- g++
+ ${os_arch_compiler}
${os_arch_option}
-fpic
@@ -162,21 +167,23 @@
src/main/native/src
jni-library.cpp
+ HeapAnalyzer.cpp
generic-classic
- g++
+ ${os_arch_compiler}
${os_arch_option}
-fpic
-shared
+ -std=c++17
-o
target
- g++
+ ${os_arch_compiler}
${os_arch_option}
-fpic
diff --git a/arthas-vmtool/src/main/java/arthas/VmTool.java b/arthas-vmtool/src/main/java/arthas/VmTool.java
index 866d47ecb08..a2ecc373a26 100644
--- a/arthas-vmtool/src/main/java/arthas/VmTool.java
+++ b/arthas-vmtool/src/main/java/arthas/VmTool.java
@@ -65,6 +65,10 @@ public static synchronized VmTool getInstance(String libPath) {
*/
private static synchronized native Class>[] getAllLoadedClasses0(Class> klass);
+ private static synchronized native String heapAnalyze0(int classNum, int objectNum);
+
+ private static synchronized native String referenceAnalyze0(Class> klass, int objectNum, int backtraceNum);
+
@Override
public void forceGc() {
forceGc0();
@@ -103,4 +107,13 @@ public Class>[] getAllLoadedClasses() {
return getAllLoadedClasses0(Class.class);
}
+ @Override
+ public String heapAnalyze(int classNum, int objectNum) {
+ return heapAnalyze0(classNum, objectNum);
+ }
+
+ @Override
+ public String referenceAnalyze(Class> klass, int objectNum, int backtraceNum) {
+ return referenceAnalyze0(klass, objectNum, backtraceNum);
+ }
}
diff --git a/arthas-vmtool/src/main/java/arthas/VmToolMXBean.java b/arthas-vmtool/src/main/java/arthas/VmToolMXBean.java
index f2100011558..02a90c24e74 100644
--- a/arthas-vmtool/src/main/java/arthas/VmToolMXBean.java
+++ b/arthas-vmtool/src/main/java/arthas/VmToolMXBean.java
@@ -50,4 +50,8 @@ public interface VmToolMXBean {
* 获取所有已加载的类
*/
public Class>[] getAllLoadedClasses();
+
+ public String heapAnalyze(int classNum, int objectNum);
+
+ public String referenceAnalyze(Class> klass, int objectNum, int backtraceNum);
}
diff --git a/arthas-vmtool/src/main/native/CMakeLists.txt b/arthas-vmtool/src/main/native/CMakeLists.txt
index 68c624afbcf..4f0aa3c005b 100644
--- a/arthas-vmtool/src/main/native/CMakeLists.txt
+++ b/arthas-vmtool/src/main/native/CMakeLists.txt
@@ -3,7 +3,7 @@ project(arthas-native)
set(CMAKE_CXX_STANDARD 14)
-add_library(jni-lib SHARED src/jni-library.cpp)
+add_library(jni-lib SHARED src/jni-library.cpp src/HeapAnalyzer.cpp src/HeapAnalyzer.h)
#使用环境变量来include,把不同系统的兼容问题交给jdk解决
include_directories("include")
diff --git a/arthas-vmtool/src/main/native/src/HeapAnalyzer.cpp b/arthas-vmtool/src/main/native/src/HeapAnalyzer.cpp
new file mode 100644
index 00000000000..1f4fe58b4a7
--- /dev/null
+++ b/arthas-vmtool/src/main/native/src/HeapAnalyzer.cpp
@@ -0,0 +1,371 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "HeapAnalyzer.h"
+
+using std::shared_ptr;
+using std::string;
+
+static void check_error(jvmtiError err, char const *name) {
+ if (err) {
+ fprintf(stderr, "ERROR: JVMTI %s failed!\n", name);
+ }
+}
+
+template
+static void append_format(string &output, const char *data, Args... args) {
+ size_t size = 1 + snprintf(0, 0, data, args...);
+ char bytes[size];
+ snprintf(bytes, size, data, args...);
+ output.append(bytes);
+}
+
+static shared_ptr getClassName(jvmtiEnv *jvmti, jclass cls) {
+ char *sig, *name;
+ check_error(jvmti->GetClassSignature(cls, &sig, 0), "GetClassSignature");
+ if (sig) {
+ int index = 0;
+ int l = strlen(sig);
+ if (l > 1 && sig[l - 1] == ';') {
+ l--;
+ }
+ while (sig[index] == '[') {
+ index++;
+ }
+ int type = 0;
+ char buf[10];
+ switch (sig[index]) {
+ case 'Z':
+ strcpy(buf, "boolean");
+ l = 7;
+ break;
+ case 'B':
+ strcpy(buf, "byte");
+ l = 4;
+ break;
+ case 'C':
+ strcpy(buf, "char");
+ l = 4;
+ break;
+ case 'S':
+ strcpy(buf, "short");
+ l = 5;
+ break;
+ case 'I':
+ strcpy(buf, "int");
+ l = 3;
+ break;
+ case 'J':
+ strcpy(buf, "long");
+ l = 4;
+ break;
+ case 'F':
+ strcpy(buf, "float");
+ l = 5;
+ break;
+ case 'D':
+ strcpy(buf, "double");
+ l = 6;
+ break;
+ case 'L': {
+ type = 1;
+ l = l - index - 1;
+ break;
+ }
+ default: {
+ type = 2;
+ l = l - index;
+ break;
+ }
+ }
+ name = new char[l + index * 2 + 1];
+ if (type == 0) {
+ strncpy(name, buf, l);
+ } else if (type == 1) {
+ strncpy(name, sig + index + 1, l);
+ } else if (type == 2) {
+ strncpy(name, sig + index, l);
+ }
+ while (index--) {
+ name[l++] = '[';
+ name[l++] = ']';
+ }
+ name[l] = 0;
+ check_error(jvmti->Deallocate((unsigned char *)sig), "Deallocate");
+ char *t = name;
+ while (*t) {
+ if (*t == '/') {
+ *t = '.';
+ }
+ t++;
+ }
+ }
+ shared_ptr sp(name);
+ return sp;
+}
+
+void ObjectInfoHeap::swap(int i, int j) {
+ shared_ptr t = array[i];
+ array[i] = array[j];
+ array[j] = t;
+}
+
+void ObjectInfoHeap::adjust(int cur, int limit) {
+ int l = 2 * cur + 1, r = 2 * cur + 2, min;
+ if (l < limit && array[l]->less(array[cur])) {
+ min = l;
+ } else {
+ min = cur;
+ }
+ if (r < limit && array[r]->less(array[min])) {
+ min = r;
+ }
+ if (min != cur) {
+ swap(min, cur);
+ adjust(min, limit);
+ }
+}
+
+void ObjectInfoHeap::sort() {
+ for (int i = record_number - 1; i >= 0; i--) {
+ swap(0, i);
+ adjust(0, i);
+ }
+}
+
+ObjectInfoHeap::ObjectInfoHeap(int record_number)
+ : record_number(record_number) {
+ array = shared_ptr[]>(
+ new shared_ptr[record_number]);
+ for (int i = 0; i < record_number; i++) {
+ array[i] = shared_ptr(new ObjectInfo());
+ }
+}
+
+void ObjectInfoHeap::add(int size, TagInfo *tag) {
+ if (array[0]->size < size) {
+ array[0]->size = size;
+ array[0]->object_tag = tag;
+ adjust(0, record_number);
+ }
+}
+
+void ObjectInfoHeap::print(shared_ptr[]> class_info_array,
+ jvmtiEnv *jvmti, string &output,
+ int backtrace_number) {
+ sort();
+ append_format(output, "\n%-4s\t%-10s\t%s\n", "id", "#bytes",
+ backtrace_number ? "class_name & references" : "class_name");
+ append_format(output,
+ "----------------------------------------------------\n");
+ for (int i = 0; i < record_number && array[i]->object_tag != 0; i++) {
+ shared_ptr oi = array[i];
+ shared_ptr ci = class_info_array[oi->object_tag->class_tag];
+ append_format(output, "%-4d\t%-10d\t%s", i + 1, oi->size, ci->name.get());
+ if (backtrace_number != 0) {
+ TagInfo *ref = oi->object_tag->referrer;
+ TagInfo *ref_pre = oi->object_tag;
+ for (int j = 0;
+ (backtrace_number == -1 || j < backtrace_number - 1) && ref != 0;
+ j++) {
+ ci = class_info_array[ref->class_tag];
+ append_format(output, " <-- %s", ci->name.get());
+ ref_pre = ref;
+ ref = ref->referrer;
+ }
+ if (ref == 0) {
+ append_format(output, " <-- root");
+ if (ref_pre->stack_info != 0) {
+ char *name;
+ check_error(
+ jvmti->GetMethodName(ref_pre->stack_info->method, &name, 0, 0),
+ "GetMethodName");
+ append_format(output, "(local variable in method: %s)\n", name);
+ check_error(jvmti->Deallocate((unsigned char *)name), "Deallocate");
+ } else {
+ append_format(output, "\n");
+ }
+ } else {
+ append_format(output, " <-- ...\n");
+ }
+ } else {
+ append_format(output, "\n");
+ }
+ }
+ append_format(output, "\n");
+}
+
+jint JNICALL HeapAnalyzer::tag(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) {
+ // java.lang.Class对象的tag已经在get_classes时设置
+ // 此处的class_tag与java.lang.Class对象的tag相同
+ // 因此该对象TagInfo中的class_tag来源于该对象class_tag所指向的TagInfo中的class_object_tag
+ TagInfo *ti = 0;
+ if (*tag_ptr == 0) {
+ ti = new TagInfo();
+ *tag_ptr = (jlong)ti;
+ } else {
+ ti = (TagInfo *)*tag_ptr;
+ }
+ if (ti->class_tag == 0) {
+ TagInfo *ctti = (TagInfo *)class_tag;
+ ti->class_tag = ctti->class_object_tag;
+ if (ti->referrer == 0) {
+ // 当引用者是root时,referrer_tag_ptr为0
+ if (referrer_tag_ptr != 0) {
+ ti->referrer = (TagInfo *)*referrer_tag_ptr;
+ } else {
+ if (reference_kind == JVMTI_HEAP_REFERENCE_STACK_LOCAL) {
+ ti->stack_info = shared_ptr(
+ new jvmtiHeapReferenceInfoStackLocal);
+ memcpy(ti->stack_info.get(), reference_info,
+ sizeof(jvmtiHeapReferenceInfoStackLocal));
+ }
+ }
+ }
+ shared_ptr[]> class_info_array =
+ *(shared_ptr[]> *)((void **)user_data)[0];
+ int *object_number_pointer = (int *)((void **)user_data)[1];
+ ObjectInfoHeap *object_info_heap =
+ (ObjectInfoHeap *)((void **)user_data)[2];
+ shared_ptr ci = class_info_array[ti->class_tag];
+ ci->instance_count++;
+ ci->total_size += size;
+ (*object_number_pointer)++;
+ if (object_info_heap != 0) {
+ object_info_heap->add(size, ti);
+ }
+ }
+ return JVMTI_VISIT_OBJECTS;
+}
+
+jint JNICALL HeapAnalyzer::untag(jlong class_tag, jlong size, jlong *tag_ptr,
+ jint length, void *user_data) {
+ bool do_delete = (bool)user_data;
+ if (*tag_ptr != 0) {
+ if (do_delete) {
+ delete (TagInfo *)*tag_ptr;
+ }
+ *tag_ptr = 0;
+ }
+ return JVMTI_VISIT_OBJECTS;
+}
+
+jint JNICALL HeapAnalyzer::reference(jlong class_tag, jlong size,
+ jlong *tag_ptr, jint length,
+ void *user_data) {
+ TagInfo *ti = (TagInfo *)*tag_ptr;
+ ObjectInfoHeap *object_info_heap = (ObjectInfoHeap *)user_data;
+ if (object_info_heap != 0) {
+ object_info_heap->add(size, ti);
+ }
+ return JVMTI_ITERATION_CONTINUE;
+}
+
+HeapAnalyzer::HeapAnalyzer(jvmtiEnv *jvmti) : jvmti(jvmti) {
+ jvmtiCapabilities capa;
+ memset(&capa, 0, sizeof(capa));
+ capa.can_tag_objects = 1;
+ check_error(jvmti->AddCapabilities(&capa), "AddCapabilities");
+ untag_objects(false);
+ get_classes();
+}
+
+HeapAnalyzer::~HeapAnalyzer() {
+ untag_objects(true);
+ jvmti = 0;
+}
+
+void HeapAnalyzer::tag_objects(ObjectInfoHeap *object_info_heap) {
+ jvmtiHeapCallbacks heap_callbacks;
+ memset(&heap_callbacks, 0, sizeof(heap_callbacks));
+ heap_callbacks.heap_reference_callback = HeapAnalyzer::tag;
+ void *user_data[3] = {(void *)&class_info_array, (void *)&object_number,
+ (void *)object_info_heap};
+ check_error(jvmti->FollowReferences(0, 0, 0, &heap_callbacks, user_data),
+ "FollowReferences");
+}
+
+void HeapAnalyzer::untag_objects(bool do_delete) {
+ jvmtiHeapCallbacks heap_callbacks;
+ memset(&heap_callbacks, 0, sizeof(heap_callbacks));
+ heap_callbacks.heap_iteration_callback = HeapAnalyzer::untag;
+ check_error(jvmti->IterateThroughHeap(JVMTI_HEAP_FILTER_UNTAGGED, 0,
+ &heap_callbacks, (void *)do_delete),
+ "IterateThroughHeap");
+}
+
+void HeapAnalyzer::get_classes() {
+ jclass *classes;
+ check_error(jvmti->GetLoadedClasses(&class_number, &classes),
+ "GetLoadedClasses");
+ class_info_array = shared_ptr[]>(
+ new shared_ptr[class_number + 1]);
+ for (int i = 1; i < class_number + 1; i++) {
+ class_info_array[i] = shared_ptr(
+ new ClassInfo(i, getClassName(jvmti, classes[i - 1])));
+ ;
+ TagInfo *ti = new TagInfo(i);
+ check_error(jvmti->SetTag(classes[i - 1], (jlong)ti), "SetTag");
+ }
+ check_error(jvmti->Deallocate((unsigned char *)classes), "Deallocate");
+}
+
+char *HeapAnalyzer::heap_analyze(int class_show_number,
+ int object_show_number) {
+ string output;
+ append_format(output, "class_number: %d\n", class_number);
+ ObjectInfoHeap *object_info_heap = new ObjectInfoHeap(object_show_number);
+ tag_objects(object_info_heap);
+ append_format(output, "object_number: %d\n", object_number);
+ object_info_heap->print(class_info_array, jvmti, output, 0);
+
+ std::sort(class_info_array.get() + 1, class_info_array.get() + class_number,
+ ClassInfo::compare);
+ append_format(output, "\n%-4s\t%-12s\t%-15s\t%s\n", "id", "#instances",
+ "#bytes", "class_name");
+ append_format(output,
+ "----------------------------------------------------\n");
+ for (int i = 1; i - 1 < class_show_number && i < class_number; i++) {
+ shared_ptr ci = class_info_array[i];
+ append_format(output, "%-4d\t%-12d\t%-15ld\t%s\n", i, ci->instance_count,
+ ci->total_size, ci->name.get());
+ }
+ append_format(output, "\n");
+
+ delete object_info_heap;
+ char *result = (char *)malloc(output.size() + 1);
+ strcpy(result, output.c_str());
+ return result;
+}
+
+char *HeapAnalyzer::reference_analyze(jclass klass, int object_show_number,
+ int backtrace_number) {
+ string output;
+ tag_objects(0);
+ ObjectInfoHeap *object_info_heap = new ObjectInfoHeap(object_show_number);
+ jvmtiHeapCallbacks heap_callbacks;
+ memset(&heap_callbacks, 0, sizeof(heap_callbacks));
+ heap_callbacks.heap_iteration_callback = HeapAnalyzer::reference;
+ check_error(jvmti->IterateThroughHeap(JVMTI_HEAP_FILTER_UNTAGGED, klass,
+ &heap_callbacks,
+ (void *)object_info_heap),
+ "IterateThroughHeap");
+ object_info_heap->print(class_info_array, jvmti, output, backtrace_number);
+
+ delete object_info_heap;
+ char *result = (char *)malloc(output.size() + 1);
+ strcpy(result, output.c_str());
+ return result;
+}
diff --git a/arthas-vmtool/src/main/native/src/HeapAnalyzer.h b/arthas-vmtool/src/main/native/src/HeapAnalyzer.h
new file mode 100644
index 00000000000..0c6f7703da8
--- /dev/null
+++ b/arthas-vmtool/src/main/native/src/HeapAnalyzer.h
@@ -0,0 +1,96 @@
+#ifndef HEAP_ANALYZER_H
+#define HEAP_ANALYZER_H
+
+#include
+#include
+
+#include
+#include
+#include
+
+using std::shared_ptr;
+using std::string;
+
+class ClassInfo {
+public:
+ int id = 0;
+ shared_ptr name;
+ int instance_count = 0;
+ long total_size = 0;
+
+ ClassInfo(int id, shared_ptr name) : id(id) {
+ this->name = name;
+ }
+ static bool compare(shared_ptr ci1, shared_ptr ci2) {
+ return ci1->total_size > ci2->total_size;
+ }
+};
+
+class TagInfo {
+public:
+ int class_tag = 0; // 该对象所属类的tag
+ int class_object_tag = 0; // 用于java.lang.Class对象标注其表示的类的tag
+ // 如果A是一个java.lang.Class的对象,其表示类A_class,则其class_tag指向java.lang.Class,而class_object_tag指向A_class
+ TagInfo *referrer = 0; // 引用者,为0时表示由root(JNI、stack等)引用
+ shared_ptr
+ stack_info; // 当引用来自JVMTI_HEAP_REFERENCE_STACK_LOCAL时,记录其reference_info
+
+ TagInfo(int class_object_tag = 0) : class_object_tag(class_object_tag) {}
+};
+
+class ObjectInfo {
+public:
+ int size = 0;
+ TagInfo *object_tag = 0;
+
+ ObjectInfo(int size, TagInfo *object_tag)
+ : size(size), object_tag(object_tag) {}
+ ObjectInfo() {}
+ bool less(shared_ptr oi) { return this->size < oi->size; }
+};
+
+class ObjectInfoHeap {
+private:
+ int record_number = 0;
+ shared_ptr[]> array;
+
+ void swap(int i, int j);
+ void adjust(int cur, int limit);
+ void sort();
+
+public:
+ ObjectInfoHeap(int record_number);
+ void add(int size, TagInfo *tag);
+ void print(shared_ptr[]> class_info_array,
+ jvmtiEnv *jvmti, string &output, int backtrace_number);
+};
+
+class HeapAnalyzer {
+private:
+ int object_number = 0;
+ jint class_number = 0;
+ shared_ptr[]> class_info_array;
+ jvmtiEnv *jvmti = 0;
+
+ static jint JNICALL tag(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);
+ static jint JNICALL untag(jlong class_tag, jlong size, jlong *tag_ptr,
+ jint length, void *user_data);
+ static jint JNICALL reference(jlong class_tag, jlong size, jlong *tag_ptr,
+ jint length, void *user_data);
+ void get_classes();
+ void tag_objects(ObjectInfoHeap *object_info_heap);
+ void untag_objects(bool do_delete);
+
+public:
+ HeapAnalyzer(jvmtiEnv *jvmti);
+ ~HeapAnalyzer();
+ char *heap_analyze(int class_show_number = 20, int object_show_number = 20);
+ char *reference_analyze(jclass klass, int object_show_number = 20,
+ int backtrace_number = 2);
+};
+
+#endif
diff --git a/arthas-vmtool/src/main/native/src/jni-library.cpp b/arthas-vmtool/src/main/native/src/jni-library.cpp
index c0bbc33ad8a..bbbd662cea8 100644
--- a/arthas-vmtool/src/main/native/src/jni-library.cpp
+++ b/arthas-vmtool/src/main/native/src/jni-library.cpp
@@ -3,7 +3,7 @@
#include
#include
#include "arthas_VmTool.h" // under target/native/javah/
-
+#include "HeapAnalyzer.h"
static jvmtiEnv *jvmti;
static jlong tagCounter = 0;
@@ -204,4 +204,23 @@ JNIEXPORT jobjectArray JNICALL Java_arthas_VmTool_getAllLoadedClasses0
}
jvmti->Deallocate(reinterpret_cast(classes));
return array;
-}
\ No newline at end of file
+}
+
+extern "C"
+JNIEXPORT jstring JNICALL Java_arthas_VmTool_heapAnalyze0
+ (JNIEnv *env, jclass thisClass, jint classNum, jint objectNum) {
+ HeapAnalyzer ha(jvmti);
+ char *result = ha.heap_analyze(classNum, objectNum);
+ jstring s = env->NewStringUTF(result);
+ free(result);
+ return s;
+}
+
+extern "C" JNIEXPORT jstring JNICALL Java_arthas_VmTool_referenceAnalyze0(
+ JNIEnv *env, jclass thisClass, jclass klass, jint objectNum, jint backtraceNum) {
+ HeapAnalyzer ha(jvmti);
+ char *result = ha.reference_analyze(klass, objectNum, backtraceNum);
+ jstring s = env->NewStringUTF(result);
+ free(result);
+ return s;
+}
diff --git a/arthas-vmtool/src/test/java/arthas/VmToolTest.java b/arthas-vmtool/src/test/java/arthas/VmToolTest.java
index 8a573f7e182..1be60da6866 100644
--- a/arthas-vmtool/src/test/java/arthas/VmToolTest.java
+++ b/arthas-vmtool/src/test/java/arthas/VmToolTest.java
@@ -182,4 +182,16 @@ public void test_getInstances_interface() {
Assertions.assertThat(interfaceInstances[0]).isEqualTo(ObjectInstances[0]);
}
+
+ @Test
+ public void testHeapAnalyzer() {
+ VmTool vmtool = initVmTool();
+ System.out.println(vmtool.heapAnalyze(20, 20));
+ }
+
+ @Test
+ public void testReferenceAnalyzer() {
+ VmTool vmtool = initVmTool();
+ System.out.println(vmtool.referenceAnalyze(String.class, 20, 3));
+ }
}
diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/VmToolCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/VmToolCommand.java
index 4dd89297c01..2ce7eb38fe9 100644
--- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/VmToolCommand.java
+++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/VmToolCommand.java
@@ -55,6 +55,8 @@
+ " vmtool --action getInstances --className java.lang.String --limit 10\n"
+ " vmtool --action getInstances --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader --className org.springframework.context.ApplicationContext\n"
+ " vmtool --action forceGc\n"
+ + " vmtool --action heapAnalyze --classNum 20 --objectNum 20 --backtraceNum 2\n"
+ + " vmtool --action referenceAnalyze --className java.lang.String --objectNum 20 --backtraceNum 2\n"
+ Constants.WIKI + Constants.WIKI_HOME + "vmtool")
//@formatter:on
public class VmToolCommand extends AnnotatedCommand {
@@ -64,6 +66,10 @@ public class VmToolCommand extends AnnotatedCommand {
private String className;
private String express;
+ private int classNum = 20;
+ private int objectNum = 20;
+ private int backtraceNum = 2;
+
private String hashCode = null;
private String classLoaderClass;
/**
@@ -149,8 +155,26 @@ public void setExpress(String express) {
this.express = express;
}
+ @Option(longName = "classNum", required = false)
+ @Description("The number of class to be shown.")
+ public void setClassNum(int classNum) {
+ this.classNum = classNum;
+ }
+
+ @Option(longName = "objectNum", required = false)
+ @Description("The number of object to be shown.")
+ public void setObjectNum(int objectNum) {
+ this.objectNum = objectNum;
+ }
+
+ @Option(longName = "backtraceNum", required = false)
+ @Description("The steps of backtrace by reference.")
+ public void setBacktraceNum(int backtraceNum) {
+ this.backtraceNum = backtraceNum;
+ }
+
public enum VmToolAction {
- getInstances, forceGc
+ getInstances, forceGc, heapAnalyze, referenceAnalyze
}
@Override
@@ -158,7 +182,7 @@ public void process(final CommandProcess process) {
try {
Instrumentation inst = process.session().getInstrumentation();
- if (VmToolAction.getInstances.equals(action)) {
+ if (VmToolAction.getInstances.equals(action) || VmToolAction.referenceAnalyze.equals(action)) {
if (className == null) {
process.end(-1, "The className option cannot be empty!");
return;
@@ -203,28 +227,41 @@ public void process(final CommandProcess process) {
process.end(-1, "Found more than one class: " + matchedClasses + ", please specify classloader with '-c '");
return;
} else {
- Object[] instances = vmToolInstance().getInstances(matchedClasses.get(0), limit);
- Object value = instances;
- if (express != null) {
- Express unpooledExpress = ExpressFactory.unpooledExpress(classLoader);
- try {
- value = unpooledExpress.bind(new InstancesWrapper(instances)).get(express);
- } catch (ExpressException e) {
- logger.warn("ognl: failed execute express: " + express, e);
- process.end(-1, "Failed to execute ognl, exception message: " + e.getMessage()
- + ", please check $HOME/logs/arthas/arthas.log for more details. ");
+ if (VmToolAction.getInstances.equals(action)) {
+ Object[] instances = vmToolInstance().getInstances(matchedClasses.get(0), limit);
+ Object value = instances;
+ if (express != null) {
+ Express unpooledExpress = ExpressFactory.unpooledExpress(classLoader);
+ try {
+ value = unpooledExpress.bind(new InstancesWrapper(instances)).get(express);
+ } catch (ExpressException e) {
+ logger.warn("ognl: failed execute express: " + express, e);
+ process.end(-1, "Failed to execute ognl, exception message: " + e.getMessage()
+ + ", please check $HOME/logs/arthas/arthas.log for more details. ");
+ }
}
- }
- process.write(new ObjectView(value, this.expand).draw());
- process.write("\n");
- process.end();
+ process.write(new ObjectView(value, this.expand).draw());
+ process.write("\n");
+ process.end();
+ } else {
+ String result = vmToolInstance().referenceAnalyze(matchedClasses.get(0), objectNum,
+ backtraceNum);
+ process.write(result);
+ process.end();
+ return;
+ }
}
} else if (VmToolAction.forceGc.equals(action)) {
vmToolInstance().forceGc();
process.write("\n");
process.end();
return;
+ } else if (VmToolAction.heapAnalyze.equals(action)) {
+ String result = vmToolInstance().heapAnalyze(classNum, objectNum);
+ process.write(result);
+ process.end();
+ return;
}
process.end();
diff --git a/lib/libArthasJniLibrary-x64.dll b/lib/libArthasJniLibrary-x64.dll
index be253538b72..c2b1cdf05b5 100644
Binary files a/lib/libArthasJniLibrary-x64.dll and b/lib/libArthasJniLibrary-x64.dll differ
diff --git a/lib/libArthasJniLibrary-x64.dylib b/lib/libArthasJniLibrary-x64.dylib
index f1e977585b9..0fdfcfe8a9f 100644
Binary files a/lib/libArthasJniLibrary-x64.dylib and b/lib/libArthasJniLibrary-x64.dylib differ
diff --git a/lib/libArthasJniLibrary-x64.so b/lib/libArthasJniLibrary-x64.so
index bd4edc7b360..4fa3407ca3b 100644
Binary files a/lib/libArthasJniLibrary-x64.so and b/lib/libArthasJniLibrary-x64.so differ
diff --git a/site/src/site/sphinx/en/vmtool.md b/site/src/site/sphinx/en/vmtool.md
index f44eab10b26..2dcddd363af 100644
--- a/site/src/site/sphinx/en/vmtool.md
+++ b/site/src/site/sphinx/en/vmtool.md
@@ -81,4 +81,48 @@ vmtool --action getInstances --classLoaderClass org.springframework.boot.loader.
vmtool --action forceGc
```
-* Use the [`vmoption`](vmoption.md) command to dynamically turn on the `PrintGC` option.
\ No newline at end of file
+* Use the [`vmoption`](vmoption.md) command to dynamically turn on the `PrintGC` option.
+
+### Analyze heap usage
+
+```bash
+$ vmtool -a heapAnalyze --classNum 5 --objectNum 3
+class_number: 4101
+object_number: 107299
+
+id #bytes class_name
+----------------------------------------------------
+1 209715216 byte[]
+2 104857616 byte[]
+3 524304 char[]
+
+
+id #instances #bytes class_name
+----------------------------------------------------
+1 7043 327124360 byte[]
+2 20303 5660096 char[]
+3 2936 631136 java.lang.Object[]
+4 20270 486480 java.lang.String
+5 4110 462904 java.lang.Class
+```
+
+> Use the `--classNum` parameter to specify classes that will be shown, use the `--objectNum` parameter to specify objects that will be shown.
+
+# Analyze reference
+
+Use `referenceAnalyze` to show reference among objects to help locate them.
+
+```bash
+$ vmtool -a referenceAnalyze --className ByteHolder --objectNum 2 --backtraceNum -1
+
+id #bytes class_name & references
+----------------------------------------------------
+1 16 ByteHolder <-- root(local variable in method: main)
+2 16 ByteHolder <-- root(local variable in method: sleep)
+```
+
+> Use the `--className` parameter to specify class name, use the `--objectNum` parameter to specify objects that will be shown, use the `--backtraceNum` parameter to specify how many times of backtrace by references among objects will be done, and set `--backtraceNum` as -1 to make backtrace do not finish until root is reached.
+
+> `classLoaderClass` and `classloader` in `getInstances` is applicable here.
+
+> If the root reference(the first reference from root, such as stack) of objects is stack of java threads, then the method name will be printed in `root(local variable in method: sleep)` , otherwise only `root` will be printed.
diff --git a/site/src/site/sphinx/vmtool.md b/site/src/site/sphinx/vmtool.md
index e78a0a82272..6519a03bd05 100644
--- a/site/src/site/sphinx/vmtool.md
+++ b/site/src/site/sphinx/vmtool.md
@@ -82,4 +82,46 @@ vmtool --action getInstances --classLoaderClass org.springframework.boot.loader.
vmtool --action forceGc
```
-* 可以结合 [`vmoption`](vmoption.md) 命令动态打开`PrintGC`开关。
\ No newline at end of file
+* 可以结合 [`vmoption`](vmoption.md) 命令动态打开`PrintGC`开关。
+
+### 分析占用最大堆内存的对象和类
+
+```bash
+$ vmtool -a heapAnalyze --classNum 5 --objectNum 3
+class_number: 4101
+object_number: 107299
+
+id #bytes class_name
+----------------------------------------------------
+1 209715216 byte[]
+2 104857616 byte[]
+3 524304 char[]
+
+
+id #instances #bytes class_name
+----------------------------------------------------
+1 7043 327124360 byte[]
+2 20303 5660096 char[]
+3 2936 631136 java.lang.Object[]
+4 20270 486480 java.lang.String
+5 4110 462904 java.lang.Class
+```
+
+> 通过 `--classNum` 参数指定输出的类数量,通过 `--objectNum` 参数指定输出的对象数量。
+
+### 分析对象间引用关系
+
+```bash
+$ vmtool -a referenceAnalyze --className ByteHolder --objectNum 2 --backtraceNum -1
+
+id #bytes class_name & references
+----------------------------------------------------
+1 16 ByteHolder <-- root(local variable in method: main)
+2 16 ByteHolder <-- root(local variable in method: sleep)
+```
+
+> 通过 `--className` 参数指定类名,通过 `--objectNum` 参数指定输出的对象数量,通过 `--backtraceNum` 参数指定回溯对象引用关系的层级,如果 `--backtraceNum` 被设置为-1,则表示不断回溯,直到找到根引用。
+
+> `getInstances` 中的 `classLoaderClass` 和 `classloader` 参数在此也适用。
+
+> 如果对象的根引用是线程栈,那么在输出的 `root(local variable in method: sleep)` 中会显式输出该引用所处的栈帧方法名,而当对象的根引用来自其他位置,例如JNI栈帧时,无法获得其方法名,只会输出 `root` 。
diff --git a/tutorials/katacoda/command-vmtool-cn/index.json b/tutorials/katacoda/command-vmtool-cn/index.json
index fa7fa03cac8..30f3ecff943 100644
--- a/tutorials/katacoda/command-vmtool-cn/index.json
+++ b/tutorials/katacoda/command-vmtool-cn/index.json
@@ -28,6 +28,10 @@
{
"title": "Arthas vmtool 命令",
"text": "vmtool-gc.md"
+ },
+ {
+ "title": "Arthas vmtool 命令",
+ "text": "vmtool-heapAnalyze.md"
}
],
"intro": {
diff --git a/tutorials/katacoda/command-vmtool-cn/vmtool-heapAnalyze.md b/tutorials/katacoda/command-vmtool-cn/vmtool-heapAnalyze.md
new file mode 100644
index 00000000000..17858b586ef
--- /dev/null
+++ b/tutorials/katacoda/command-vmtool-cn/vmtool-heapAnalyze.md
@@ -0,0 +1,15 @@
+### 分析占用最大堆内存的对象和类
+
+`vmtool -a heapAnalyze --classNum 5 --objectNum 3`{{execute T2}}
+
+> 通过 `--classNum` 参数指定输出的类数量,通过 `--objectNum` 参数指定输出的对象数量。
+
+### 分析对象间引用关系
+
+`vmtool -a referenceAnalyze --className java.lang.String --objectNum 2 --backtraceNum -1`{{execute T2}}
+
+> 通过 `--className` 参数指定类名,通过 `--objectNum` 参数指定输出的对象数量,通过 `--backtraceNum` 参数指定回溯对象引用关系的层级,如果 `--backtraceNum` 被设置为-1,则表示不断回溯,直到找到根引用。
+
+> `getInstances` 中的 `classLoaderClass` 和 `classloader` 参数在此也适用。
+
+> 如果对象的根引用是线程栈,那么在输出的中会显式输出该引用所处的栈帧方法名,而当对象的根引用来自其他位置,例如JNI栈帧时,无法获得其方法名,只会输出 `root` 。
diff --git a/tutorials/katacoda/command-vmtool-en/index.json b/tutorials/katacoda/command-vmtool-en/index.json
index e314aafebc7..63fb4a1235a 100644
--- a/tutorials/katacoda/command-vmtool-en/index.json
+++ b/tutorials/katacoda/command-vmtool-en/index.json
@@ -28,6 +28,10 @@
{
"title": "Arthas vmtool command",
"text": "vmtool-gc.md"
+ },
+ {
+ "title": "Arthas vmtool command",
+ "text": "vmtool-heapAnalyze.md"
}
],
"intro": {
diff --git a/tutorials/katacoda/command-vmtool-en/vmtool-heapAnalyze.md b/tutorials/katacoda/command-vmtool-en/vmtool-heapAnalyze.md
new file mode 100644
index 00000000000..8aa82e1f035
--- /dev/null
+++ b/tutorials/katacoda/command-vmtool-en/vmtool-heapAnalyze.md
@@ -0,0 +1,17 @@
+### Analyze heap usage
+
+`vmtool -a heapAnalyze --classNum 5 --objectNum 3`{{execute T2}}
+
+> Use the `--classNum` parameter to specify classes that will be shown, use the `--objectNum` parameter to specify objects that will be shown.
+
+# Analyze reference
+
+Use `referenceAnalyze` to show reference among objects to help locate them.
+
+`vmtool -a referenceAnalyze --className ByteHolder --objectNum 2 --backtraceNum -1`{{execute T2}}
+
+> Use the `--className` parameter to specify class name, use the `--objectNum` parameter to specify objects that will be shown, use the `--backtraceNum` parameter to specify how many times of backtrace by references among objects will be done, and set `--backtraceNum` as -1 to make backtrace do not finish until root is reached.
+
+> `classLoaderClass` and `classloader` in `getInstances` is applicable here.
+
+> If the root reference(the first reference from root, such as stack) of objects is stack of java threads, then the method name will be printed, otherwise only `root` will be printed.