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.