如果让你查找出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;
}

上面的代码具体干了下面的几件事情,

  1. 根据JNIEnv获取JavaVM,然后根据JavaVM获取jvmtiEnv
  2. 设置当前jvmtiEnv可以做的操作,这里设置jvmtiEnv可以对堆中的对象打tag
  3. 调用IterateOverInstancesOfClass遍历所有的java.lang.Object的实例,然后打tag,这里的tag使用999999
  4. 获取所有打了tag的对象,tag为999999,保存到instances中
  5. 将所有对象保存到一个jobjectArray中
  6. 释放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