From 6623ef44c6017d5bc52a316ac9d731a37254ed82 Mon Sep 17 00:00:00 2001 From: dragon-zhang <38336731+dragon-zhang@users.noreply.github.com> Date: Fri, 30 Apr 2021 16:31:44 +0800 Subject: [PATCH] return array in VmTool native method, fix jni memory leak problem #1781 --- .../src/main/java/arthas/VmTool.java | 13 +- .../src/main/java/arthas/VmToolMXBean.java | 6 +- .../src/main/native/include/arthas_VmTool.h | 26 ++-- .../src/main/native/src/jni-library.cpp | 44 ++++--- .../src/test/java/arthas/VmToolTest.java | 114 ++++++++++++++++-- .../command/monitor200/VmToolCommand.java | 3 +- pom.xml | 5 - spy/src/main/java/arthas/VmTool.java | 13 +- spy/src/main/java/arthas/VmToolMXBean.java | 6 +- 9 files changed, 159 insertions(+), 71 deletions(-) diff --git a/arthas-vmtool/src/main/java/arthas/VmTool.java b/arthas-vmtool/src/main/java/arthas/VmTool.java index 6f7cc0a2a..ae08763d7 100644 --- a/arthas-vmtool/src/main/java/arthas/VmTool.java +++ b/arthas-vmtool/src/main/java/arthas/VmTool.java @@ -1,7 +1,5 @@ package arthas; -import java.util.ArrayList; - /** * @author ZhangZiCheng 2021-02-12 * @author hengyunabc 2021-04-26 @@ -46,7 +44,7 @@ public class VmTool implements VmToolMXBean { /** * 获取某个class在jvm中当前所有存活实例 */ - private static native ArrayList getInstances0(Class klass); + private static native T[] getInstances0(Class klass); /** * 统计某个class在jvm中当前所有存活实例的总占用内存,单位:Byte @@ -66,13 +64,12 @@ public class VmTool implements VmToolMXBean { /** * 获取所有已加载的类 */ - private static native ArrayList> getAllLoadedClasses0(); + private static native Class[] getAllLoadedClasses0(); /** * 包括小类型(如int) */ - @SuppressWarnings("all") - public static ArrayList getAllClasses() { + public static Class[] getAllClasses() { return getInstances0(Class.class); } @@ -82,7 +79,7 @@ public class VmTool implements VmToolMXBean { } @Override - public ArrayList getInstances(Class klass) { + public T[] getInstances(Class klass) { return getInstances0(klass); } @@ -102,7 +99,7 @@ public class VmTool implements VmToolMXBean { } @Override - public ArrayList> getAllLoadedClasses() { + public Class[] getAllLoadedClasses() { return getAllLoadedClasses0(); } diff --git a/arthas-vmtool/src/main/java/arthas/VmToolMXBean.java b/arthas-vmtool/src/main/java/arthas/VmToolMXBean.java index e70a17e7a..51af311ae 100644 --- a/arthas-vmtool/src/main/java/arthas/VmToolMXBean.java +++ b/arthas-vmtool/src/main/java/arthas/VmToolMXBean.java @@ -1,7 +1,5 @@ package arthas; -import java.util.ArrayList; - /** * VmTool interface for JMX server. How to register VmTool MBean: * @@ -24,7 +22,7 @@ public interface VmToolMXBean { /** * 获取某个class在jvm中当前所有存活实例 */ - public ArrayList getInstances(Class klass); + public T[] getInstances(Class klass); /** * 统计某个class在jvm中当前所有存活实例的总占用内存,单位:Byte @@ -44,5 +42,5 @@ public interface VmToolMXBean { /** * 获取所有已加载的类 */ - public ArrayList> getAllLoadedClasses(); + public Class[] getAllLoadedClasses(); } diff --git a/arthas-vmtool/src/main/native/include/arthas_VmTool.h b/arthas-vmtool/src/main/native/include/arthas_VmTool.h index 87098bfac..3f11b40a6 100644 --- a/arthas-vmtool/src/main/native/include/arthas_VmTool.h +++ b/arthas-vmtool/src/main/native/include/arthas_VmTool.h @@ -9,7 +9,7 @@ extern "C" { #endif /* * Class: arthas_VmTool - * Method: check + * Method: check0 * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_arthas_VmTool_check0 @@ -17,42 +17,42 @@ JNIEXPORT jstring JNICALL Java_arthas_VmTool_check0 /* * Class: arthas_VmTool - * Method: getInstances - * Signature: (Ljava/lang/Class;)Ljava/util/ArrayList; + * Method: getInstances0 + * Signature: (Ljava/lang/Class;)[Ljava/lang/Object; */ -JNIEXPORT jobject JNICALL Java_arthas_VmTool_getInstances +JNIEXPORT jobjectArray JNICALL Java_arthas_VmTool_getInstances0 (JNIEnv *, jclass, jclass); /* * Class: arthas_VmTool - * Method: sumInstanceSize + * Method: sumInstanceSize0 * Signature: (Ljava/lang/Class;)J */ -JNIEXPORT jlong JNICALL Java_arthas_VmTool_sumInstanceSize +JNIEXPORT jlong JNICALL Java_arthas_VmTool_sumInstanceSize0 (JNIEnv *, jclass, jclass); /* * Class: arthas_VmTool - * Method: getInstanceSize + * Method: getInstanceSize0 * Signature: (Ljava/lang/Object;)J */ -JNIEXPORT jlong JNICALL Java_arthas_VmTool_getInstanceSize +JNIEXPORT jlong JNICALL Java_arthas_VmTool_getInstanceSize0 (JNIEnv *, jclass, jobject); /* * Class: arthas_VmTool - * Method: countInstances + * Method: countInstances0 * Signature: (Ljava/lang/Class;)J */ -JNIEXPORT jlong JNICALL Java_arthas_VmTool_countInstances +JNIEXPORT jlong JNICALL Java_arthas_VmTool_countInstances0 (JNIEnv *, jclass, jclass); /* * Class: arthas_VmTool - * Method: getAllLoadedClasses - * Signature: ()Ljava/util/ArrayList; + * Method: getAllLoadedClasses0 + * Signature: ()[Ljava/lang/Class; */ -JNIEXPORT jobject JNICALL Java_arthas_VmTool_getAllLoadedClasses +JNIEXPORT jobjectArray JNICALL Java_arthas_VmTool_getAllLoadedClasses0 (JNIEnv *, jclass); #ifdef __cplusplus diff --git a/arthas-vmtool/src/main/native/src/jni-library.cpp b/arthas-vmtool/src/main/native/src/jni-library.cpp index 776124cdf..95fab8f62 100644 --- a/arthas-vmtool/src/main/native/src/jni-library.cpp +++ b/arthas-vmtool/src/main/native/src/jni-library.cpp @@ -4,6 +4,21 @@ #include #include "arthas_VmTool.h" +//缓存 +static jclass cachedClass = NULL; + +extern "C" +JNIEXPORT jclass JNICALL getClass(JNIEnv *env) { + if (cachedClass == NULL) { + //通过其签名找到Class的Class + jclass theClass = env->FindClass("java/lang/Class"); + //放入缓存 + cachedClass = static_cast(env->NewGlobalRef(theClass)); + env->DeleteLocalRef(theClass); + } + return cachedClass; +} + extern "C" JNIEXPORT jstring JNICALL Java_arthas_VmTool_check0(JNIEnv *env, jclass thisClass) { @@ -46,7 +61,7 @@ HeapObjectCallback(jlong class_tag, jlong size, jlong *tag_ptr, void *user_data) } extern "C" -JNIEXPORT jobject JNICALL +JNIEXPORT jobjectArray JNICALL Java_arthas_VmTool_getInstances0(JNIEnv *env, jclass thisClass, jclass klass) { jvmtiEnv *jvmti = getJvmtiEnv(env); @@ -75,15 +90,13 @@ Java_arthas_VmTool_getInstances0(JNIEnv *env, jclass thisClass, jclass klass) { return JNI_FALSE; } - //通过其签名找到ArrayList的Class - jclass arrayListClass = env->FindClass("java/util/ArrayList"); - jobject arrayList = createJavaInstance(env, arrayListClass); - jmethodID addMethod = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z"); - //添加元素到ArrayList实例 + jobjectArray array = env->NewObjectArray(count, klass, NULL); + //添加元素到数组 for (int i = 0; i < count; i++) { - env->CallObjectMethod(arrayList, addMethod, instances[i]); + env->SetObjectArrayElement(array, i, instances[i]); } - return arrayList; + jvmti->Deallocate(reinterpret_cast(instances)); + return array; } extern "C" @@ -122,6 +135,7 @@ Java_arthas_VmTool_sumInstanceSize0(JNIEnv *env, jclass thisClass, jclass klass) jvmti->GetObjectSize(instances[i], &size); sum = sum + size; } + jvmti->Deallocate(reinterpret_cast(instances)); return sum; } @@ -172,7 +186,7 @@ Java_arthas_VmTool_countInstances0(JNIEnv *env, jclass thisClass, jclass klass) } extern "C" -JNIEXPORT jobject JNICALL Java_arthas_VmTool_getAllLoadedClasses0 +JNIEXPORT jobjectArray JNICALL Java_arthas_VmTool_getAllLoadedClasses0 (JNIEnv *env, jclass thisClass) { jvmtiEnv *jvmti = getJvmtiEnv(env); @@ -186,13 +200,11 @@ JNIEXPORT jobject JNICALL Java_arthas_VmTool_getAllLoadedClasses0 return JNI_FALSE; } - //通过其签名找到ArrayList的Class - jclass arrayListClass = env->FindClass("java/util/ArrayList"); - jobject arrayList = createJavaInstance(env, arrayListClass); - jmethodID addMethod = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z"); - //添加元素到ArrayList实例 + jobjectArray array = env->NewObjectArray(count, getClass(env), NULL); + //添加元素到数组 for (int i = 0; i < count; i++) { - env->CallObjectMethod(arrayList, addMethod, classes[i]); + env->SetObjectArrayElement(array, i, classes[i]); } - return arrayList; + jvmti->Deallocate(reinterpret_cast(classes)); + return array; } \ No newline at end of file diff --git a/arthas-vmtool/src/test/java/arthas/VmToolTest.java b/arthas-vmtool/src/test/java/arthas/VmToolTest.java index 381a3eaef..03d3e11a4 100644 --- a/arthas-vmtool/src/test/java/arthas/VmToolTest.java +++ b/arthas-vmtool/src/test/java/arthas/VmToolTest.java @@ -6,8 +6,11 @@ import com.taobao.arthas.common.VmToolUtils; import java.io.File; import java.lang.ref.WeakReference; -import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicLong; +/** + * 以下本地测试的jvm参数均为:-Xms128m -Xmx128m + */ public class VmToolTest { /** @@ -22,23 +25,18 @@ public class VmToolTest { * after instances->[] */ @Test - public void test01() { + public void testIsSnapshot() { try { - String path = VmTool.class.getProtectionDomain().getCodeSource().getLocation().getPath(); - System.err.println(path); - - String libPath = new File(path, VmToolUtils.detectLibName()).getAbsolutePath(); - VmTool vmtool = VmTool.getInstance(libPath); - + VmTool vmtool = initVmTool(); //调用native方法,获取已加载的类,不包括小类型(如int) - ArrayList> allLoadedClasses = vmtool.getAllLoadedClasses(); - System.out.println("allLoadedClasses->" + allLoadedClasses.size()); + Class[] allLoadedClasses = vmtool.getAllLoadedClasses(); + System.out.println("allLoadedClasses->" + allLoadedClasses.length); //通过下面的例子,可以看到getInstances(Class klass)拿到的是当前存活的所有对象 WeakReference weakReference1 = new WeakReference(new VmToolTest()); WeakReference weakReference2 = new WeakReference(new VmToolTest()); System.out.println(weakReference1.get() + " " + weakReference2.get()); - ArrayList beforeInstances = vmtool.getInstances(VmTool.class); + VmTool[] beforeInstances = vmtool.getInstances(VmTool.class); System.out.println("before instances->" + beforeInstances); System.out.println("size->" + vmtool.getInstanceSize(weakReference1.get())); System.out.println("count->" + vmtool.countInstances(VmTool.class)); @@ -48,10 +46,102 @@ public class VmToolTest { System.gc(); Thread.sleep(100); System.out.println(weakReference1.get() + " " + weakReference2.get()); - ArrayList afterInstances = vmtool.getInstances(VmTool.class); + VmTool[] afterInstances = vmtool.getInstances(VmTool.class); System.out.println("after instances->" + afterInstances); } catch (Exception e) { e.printStackTrace(); } } + + private VmTool initVmTool() { + String path = VmTool.class.getProtectionDomain().getCodeSource().getLocation().getPath(); + System.err.println(path); + + String libPath = new File(path, VmToolUtils.detectLibName()).getAbsolutePath(); + return VmTool.getInstance(libPath); + } + + @Test + public void testGetInstancesMemoryLeak() { + //这里睡20s是为了方便用jprofiler连接上进程 +// try { +// Thread.sleep(20000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } + VmTool vmtool = initVmTool(); + final AtomicLong totalTime = new AtomicLong(); + //本地测试请改成200000 + for (int i = 1; i <= 2; i++) { + long start = System.currentTimeMillis(); + WeakReference reference = new WeakReference(vmtool.getInstances(Object.class)); + Object[] instances = reference.get(); + long cost = System.currentTimeMillis() - start; + totalTime.addAndGet(cost); + System.out.println(i + " instance size:" + (instances == null ? 0 : instances.length) + ", cost " + cost + "ms avgCost " + totalTime.doubleValue() / i + "ms"); + instances = null; + System.gc(); + } + } + + @Test + public void testSumInstancesMemoryLeak() { + //这里睡20s是为了方便用jprofiler连接上进程 +// try { +// Thread.sleep(20000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } + VmTool vmtool = initVmTool(); + final AtomicLong totalTime = new AtomicLong(); + //本地测试请改成200000 + for (int i = 1; i <= 2; i++) { + long start = System.currentTimeMillis(); + long sum = vmtool.sumInstanceSize(Object.class); + long cost = System.currentTimeMillis() - start; + totalTime.addAndGet(cost); + System.out.println(i + " sum:" + sum + ", cost " + cost + "ms avgCost " + totalTime.doubleValue() / i + "ms"); + } + } + + @Test + public void testCountInstancesMemoryLeak() { + //这里睡20s是为了方便用jprofiler连接上进程 +// try { +// Thread.sleep(20000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } + VmTool vmtool = initVmTool(); + final AtomicLong totalTime = new AtomicLong(); + //本地测试请改成200000 + for (int i = 1; i <= 2; i++) { + long start = System.currentTimeMillis(); + long count = vmtool.countInstances(Object.class); + long cost = System.currentTimeMillis() - start; + totalTime.addAndGet(cost); + System.out.println(i + " count:" + count + ", cost " + cost + "ms avgCost " + totalTime.doubleValue() / i + "ms"); + } + } + + @Test + public void testGetAllLoadedClassesMemoryLeak() { + //这里睡20s是为了方便用jprofiler连接上进程 +// try { +// Thread.sleep(20000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } + VmTool vmtool = initVmTool(); + final AtomicLong totalTime = new AtomicLong(); + //本地测试请改成200000 + for (int i = 1; i <= 2; i++) { + long start = System.currentTimeMillis(); + Class[] allLoadedClasses = vmtool.getAllLoadedClasses(); + long cost = System.currentTimeMillis() - start; + totalTime.addAndGet(cost); + System.out.println(i + " class size:" + allLoadedClasses.length + ", cost " + cost + "ms avgCost " + totalTime.doubleValue() / i + "ms"); + allLoadedClasses = null; + } + } } 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 744bf2c04..bd17d6e16 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 @@ -38,6 +38,7 @@ import arthas.VmTool; /** * * @author hengyunabc 2021-04-27 + * @author ZhangZiCheng 2021-04-29 * */ //@formatter:off @@ -163,7 +164,7 @@ public class VmToolCommand extends AnnotatedCommand { process.end(-1, "Found more than one class: " + matchedClasses + "."); return; } else { - ArrayList instances = vmToolInstance().getInstances(matchedClasses.get(0)); + Object[] instances = vmToolInstance().getInstances(matchedClasses.get(0)); Object value = instances; if (express != null) { Express unpooledExpress = ExpressFactory.unpooledExpress(classLoader); diff --git a/pom.xml b/pom.xml index 207b72484..9dd037c17 100644 --- a/pom.xml +++ b/pom.xml @@ -217,11 +217,6 @@ zt-zip 1.14 - - org.scijava - native-lib-loader - 2.0.2 - diff --git a/spy/src/main/java/arthas/VmTool.java b/spy/src/main/java/arthas/VmTool.java index 6f7cc0a2a..ae08763d7 100644 --- a/spy/src/main/java/arthas/VmTool.java +++ b/spy/src/main/java/arthas/VmTool.java @@ -1,7 +1,5 @@ package arthas; -import java.util.ArrayList; - /** * @author ZhangZiCheng 2021-02-12 * @author hengyunabc 2021-04-26 @@ -46,7 +44,7 @@ public class VmTool implements VmToolMXBean { /** * 获取某个class在jvm中当前所有存活实例 */ - private static native ArrayList getInstances0(Class klass); + private static native T[] getInstances0(Class klass); /** * 统计某个class在jvm中当前所有存活实例的总占用内存,单位:Byte @@ -66,13 +64,12 @@ public class VmTool implements VmToolMXBean { /** * 获取所有已加载的类 */ - private static native ArrayList> getAllLoadedClasses0(); + private static native Class[] getAllLoadedClasses0(); /** * 包括小类型(如int) */ - @SuppressWarnings("all") - public static ArrayList getAllClasses() { + public static Class[] getAllClasses() { return getInstances0(Class.class); } @@ -82,7 +79,7 @@ public class VmTool implements VmToolMXBean { } @Override - public ArrayList getInstances(Class klass) { + public T[] getInstances(Class klass) { return getInstances0(klass); } @@ -102,7 +99,7 @@ public class VmTool implements VmToolMXBean { } @Override - public ArrayList> getAllLoadedClasses() { + public Class[] getAllLoadedClasses() { return getAllLoadedClasses0(); } diff --git a/spy/src/main/java/arthas/VmToolMXBean.java b/spy/src/main/java/arthas/VmToolMXBean.java index e70a17e7a..51af311ae 100644 --- a/spy/src/main/java/arthas/VmToolMXBean.java +++ b/spy/src/main/java/arthas/VmToolMXBean.java @@ -1,7 +1,5 @@ package arthas; -import java.util.ArrayList; - /** * VmTool interface for JMX server. How to register VmTool MBean: * @@ -24,7 +22,7 @@ public interface VmToolMXBean { /** * 获取某个class在jvm中当前所有存活实例 */ - public ArrayList getInstances(Class klass); + public T[] getInstances(Class klass); /** * 统计某个class在jvm中当前所有存活实例的总占用内存,单位:Byte @@ -44,5 +42,5 @@ public interface VmToolMXBean { /** * 获取所有已加载的类 */ - public ArrayList> getAllLoadedClasses(); + public Class[] getAllLoadedClasses(); }