如果让你查找出java堆中的所有对象你会怎么做呢?很多人听到这个问题估计会一愣,因为jdk层面没有提供这么底层的操作方法。如果你有jni编程经验的人,你可能会想到是否可以通过jni来完成呢?答案是肯定的,但是jni只是提供了java调用本地方法的途径,单单依靠jni是不够的,我们还需要机制能够和jvm打交道。如果你有过使用过一些jvm性能采样诊断工具或者了解过一些jvm底层debug机制,你可能听过jvmti(JVM Tool Interface)。jvmti提供了一系列操作jvm的方法,包括线程操作、堆栈操作、断点等。网上搜索相关资料,但是多数都是告诉你如何在java agent中使用jvmti,很少有讲如何把jni和jvmti联合起来使用的。下面的内容试图通过将jni和jvmti结合起来,解决上面提到的问题。
接口定义
首先我们定义一下我们需要的接口。
import java.util.ArrayList;
import java.util.List;
public class HeapObjectQuerier {
// native方法
public native Object[] query();
static {
// 装入动态链接库,"HeapObjectQuerier"是要装入的动态链接库名称。
System.loadLibrary("HeapObjectQuerier");
}
public static void main(String[] args) {
HeapObjectQuerier querier1 = new HeapObjectQuerier();
List<Long> a = new ArrayList<>();
HeapObjectQuerier querier = new HeapObjectQuerier();
Object[] objects = querier.query();
for (Object object : objects) {
System.out.println(object);
}
}
}
我们定义了一个HeapObjectQuerier类,该类有一个query方法,该方法是native的,用于查询堆中的所有对象。static静态块内部,加载动态链接库。
编译头文件
javah -jni HeapObjectQuerier
使用上面的命令会生一个同名的.h文件,我们看一下编译后的内容,
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HeapObjectQuerier */
#ifndef _Included_HeapObjectQuerier
#define _Included_HeapObjectQuerier
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HeapObjectQuerier
* Method: query
* Signature: (Ljava/lang/Class;)[Ljava/lang/Object;
*/
JNIEXPORT jobjectArray JNICALL Java_HeapObjectQuerier_query
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
编写功能
#include "HeapObjectQuerier.h"
#include <jni.h>
#include <jvmti.h>
#include <iostream>
#include <stdio.h>
using namespace std;
static jlong tag = 999999;
static jvmtiIterationControl JNICALL heapObjectCallback2(jlong class_tag,
jlong size, jlong* tag_ptr, void* user_data) {
*tag_ptr = tag;
return JVMTI_ITERATION_CONTINUE;
}
JNIEXPORT jobjectArray JNICALL Java_HeapObjectQuerier_query
(JNIEnv *env, jobject) {
// 1
JavaVM *jvm = NULL;
env->GetJavaVM(&jvm);
jvmtiEnv *jvmti = NULL;
jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_2);
// 2
jvmtiCapabilities capa;
memset(&capa, 0, sizeof(jvmtiCapabilities));
capa.can_tag_objects = 1;
jvmti->AddCapabilities(&capa);
// 3
jvmtiError error = jvmti->IterateOverInstancesOfClass(env->FindClass("HeapObjectQuerier"), JVMTI_HEAP_OBJECT_EITHER, heapObjectCallback2, NULL);
if (error != JVMTI_ERROR_NONE) {
return NULL;
}
// 4
jint count;
jobject *instances;
error = jvmti->GetObjectsWithTags(1, &tag, &count, &instances, NULL);
if (error != JVMTI_ERROR_NONE) {
return NULL;
}
// 5
jobjectArray result = env->NewObjectArray(count, env->FindClass("java/lang/Object"), NULL);
for(int i = 0; i < count; i++) {
env->SetObjectArrayElement(result, i, instances[i]);
}
// 6
jvmti->Deallocate(reinterpret_cast<unsigned char*>(instances));
return result;
}
上面的代码具体干了下面的几件事情,
- 根据JNIEnv获取JavaVM,然后根据JavaVM获取jvmtiEnv
- 设置当前jvmtiEnv可以做的操作,这里设置jvmtiEnv可以对堆中的对象打tag
- 调用IterateOverInstancesOfClass遍历所有的java.lang.Object的实例,然后打tag,这里的tag使用999999
- 获取所有打了tag的对象,tag为999999,保存到instances中
- 将所有对象保存到一个jobjectArray中
- 释放instances的内存
通过上面几步我们就可以得到所有的对象。
编译动态链接库
由于我使用的系统是mac系统,下面的命令可能不适用于linux,
g++ -g -dynamiclib -o libHeapObjectQuerier.jnilib HeapObjectQuerier.cpp -framework JavaVM -I/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/include/darwin
使用上面的命令可以生成一个libHeapObjectQuerier.jnilib
使用动态链接库
编译上面的java文件,然后运行,可以看到如下的输出,
HeapObjectQuerier@7852e922
HeapObjectQuerier@4e25154f