return array in VmTool native method, fix jni memory leak problem #1781

This commit is contained in:
dragon-zhang 2021-04-30 16:31:44 +08:00 committed by GitHub
parent 932340eac6
commit 6623ef44c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 159 additions and 71 deletions

View File

@ -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 <T> ArrayList<T> getInstances0(Class<T> klass);
private static native <T> T[] getInstances0(Class<T> klass);
/**
* 统计某个class在jvm中当前所有存活实例的总占用内存单位Byte
@ -66,13 +64,12 @@ public class VmTool implements VmToolMXBean {
/**
* 获取所有已加载的类
*/
private static native ArrayList<Class<?>> getAllLoadedClasses0();
private static native Class<?>[] getAllLoadedClasses0();
/**
* 包括小类型(如int)
*/
@SuppressWarnings("all")
public static ArrayList<Class> getAllClasses() {
public static Class<?>[] getAllClasses() {
return getInstances0(Class.class);
}
@ -82,7 +79,7 @@ public class VmTool implements VmToolMXBean {
}
@Override
public <T> ArrayList<T> getInstances(Class<T> klass) {
public <T> T[] getInstances(Class<T> klass) {
return getInstances0(klass);
}
@ -102,7 +99,7 @@ public class VmTool implements VmToolMXBean {
}
@Override
public ArrayList<Class<?>> getAllLoadedClasses() {
public Class<?>[] getAllLoadedClasses() {
return getAllLoadedClasses0();
}

View File

@ -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 <T> ArrayList<T> getInstances(Class<T> klass);
public <T> T[] getInstances(Class<T> klass);
/**
* 统计某个class在jvm中当前所有存活实例的总占用内存单位Byte
@ -44,5 +42,5 @@ public interface VmToolMXBean {
/**
* 获取所有已加载的类
*/
public ArrayList<Class<?>> getAllLoadedClasses();
public Class<?>[] getAllLoadedClasses();
}

View File

@ -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

View File

@ -4,6 +4,21 @@
#include <jvmti.h>
#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<jclass>(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<unsigned char *>(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<unsigned char *>(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<unsigned char *>(classes));
return array;
}

View File

@ -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<Class<?>> allLoadedClasses = vmtool.getAllLoadedClasses();
System.out.println("allLoadedClasses->" + allLoadedClasses.size());
Class<?>[] allLoadedClasses = vmtool.getAllLoadedClasses();
System.out.println("allLoadedClasses->" + allLoadedClasses.length);
//通过下面的例子可以看到getInstances(Class<T> klass)拿到的是当前存活的所有对象
WeakReference<VmToolTest> weakReference1 = new WeakReference<VmToolTest>(new VmToolTest());
WeakReference<VmToolTest> weakReference2 = new WeakReference<VmToolTest>(new VmToolTest());
System.out.println(weakReference1.get() + " " + weakReference2.get());
ArrayList<VmTool> 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<VmTool> 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<Object[]> reference = new WeakReference<Object[]>(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;
}
}
}

View File

@ -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);

View File

@ -217,11 +217,6 @@
<artifactId>zt-zip</artifactId>
<version>1.14</version>
</dependency>
<dependency>
<groupId>org.scijava</groupId>
<artifactId>native-lib-loader</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -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 <T> ArrayList<T> getInstances0(Class<T> klass);
private static native <T> T[] getInstances0(Class<T> klass);
/**
* 统计某个class在jvm中当前所有存活实例的总占用内存单位Byte
@ -66,13 +64,12 @@ public class VmTool implements VmToolMXBean {
/**
* 获取所有已加载的类
*/
private static native ArrayList<Class<?>> getAllLoadedClasses0();
private static native Class<?>[] getAllLoadedClasses0();
/**
* 包括小类型(如int)
*/
@SuppressWarnings("all")
public static ArrayList<Class> getAllClasses() {
public static Class<?>[] getAllClasses() {
return getInstances0(Class.class);
}
@ -82,7 +79,7 @@ public class VmTool implements VmToolMXBean {
}
@Override
public <T> ArrayList<T> getInstances(Class<T> klass) {
public <T> T[] getInstances(Class<T> klass) {
return getInstances0(klass);
}
@ -102,7 +99,7 @@ public class VmTool implements VmToolMXBean {
}
@Override
public ArrayList<Class<?>> getAllLoadedClasses() {
public Class<?>[] getAllLoadedClasses() {
return getAllLoadedClasses0();
}

View File

@ -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 <T> ArrayList<T> getInstances(Class<T> klass);
public <T> T[] getInstances(Class<T> klass);
/**
* 统计某个class在jvm中当前所有存活实例的总占用内存单位Byte
@ -44,5 +42,5 @@ public interface VmToolMXBean {
/**
* 获取所有已加载的类
*/
public ArrayList<Class<?>> getAllLoadedClasses();
public Class<?>[] getAllLoadedClasses();
}