如何从Java本地接口(JNI)调用getStackTrace方法
通过JNI获取Java堆栈跟踪的实现示例
我最近在啃JNI的相关知识,想着试试用它来获取Java的堆栈跟踪,先给大家看看我写的Java端代码:
package test.com.jni; public class HelloJNI { static { System.loadLibrary("hello"); // 运行时加载本地库 } // 声明native方法:获取堆栈跟踪数组 private native StackTraceElement[] getStackTraceNative(Throwable throwable); // 声明native方法:打印堆栈跟踪 private native void printStackTraceNative(Throwable throwable); public static void main(String[] args) { test(); } public static void test() { // 实例化并调用native方法测试 new HelloJNI().printStackTraceNative(new Throwable("测试异常")); } }
接下来是对应的C语言实现(文件名hello.c),这里实现了两个native方法的逻辑,核心是通过JNI调用Java中Throwable类的getStackTrace()方法来获取堆栈信息:
#include <jni.h> #include <stdio.h> // 获取JNI环境中对应的类和方法ID JNIEXPORT jobjectArray JNICALL Java_test_com_jni_HelloJNI_getStackTraceNative (JNIEnv *env, jobject obj, jobject throwable) { // 获取Throwable类 jclass throwableClass = (*env)->GetObjectClass(env, throwable); // 获取getStackTrace方法的ID:方法签名是()[Ljava/lang/StackTraceElement; jmethodID getStackTraceMethod = (*env)->GetMethodID(env, throwableClass, "getStackTrace", "()[Ljava/lang/StackTraceElement;"); // 调用getStackTrace方法,返回StackTraceElement数组 jobjectArray stackTraceArray = (jobjectArray)(*env)->CallObjectMethod(env, throwable, getStackTraceMethod); return stackTraceArray; } JNIEXPORT void JNICALL Java_test_com_jni_HelloJNI_printStackTraceNative (JNIEnv *env, jobject obj, jobject throwable) { // 先调用上面的方法获取堆栈数组 jobjectArray stackTraceArray = Java_test_com_jni_HelloJNI_getStackTraceNative(env, obj, throwable); if (stackTraceArray == NULL) { printf("无法获取堆栈跟踪\n"); return; } // 获取数组长度 jsize length = (*env)->GetArrayLength(env, stackTraceArray); // 获取StackTraceElement类 jclass stackTraceElementClass = (*env)->FindClass(env, "java/lang/StackTraceElement"); // 获取getClassName、getMethodName、getFileName、getLineNumber方法的ID jmethodID getClassNameMethod = (*env)->GetMethodID(env, stackTraceElementClass, "getClassName", "()Ljava/lang/String;"); jmethodID getMethodNameMethod = (*env)->GetMethodID(env, stackTraceElementClass, "getMethodName", "()Ljava/lang/String;"); jmethodID getFileNameMethod = (*env)->GetMethodID(env, stackTraceElementClass, "getFileName", "()Ljava/lang/String;"); jmethodID getLineNumberMethod = (*env)->GetMethodID(env, stackTraceElementClass, "getLineNumber", "()I"); // 遍历数组打印每一行堆栈信息 printf("堆栈跟踪信息:\n"); for (jsize i = 0; i < length; i++) { jobject element = (*env)->GetObjectArrayElement(env, stackTraceArray, i); // 调用方法获取各个字段值 jstring className = (jstring)(*env)->CallObjectMethod(env, element, getClassNameMethod); jstring methodName = (jstring)(*env)->CallObjectMethod(env, element, getMethodNameMethod); jstring fileName = (jstring)(*env)->CallObjectMethod(env, element, getFileNameMethod); jint lineNumber = (*env)->CallIntMethod(env, element, getLineNumberMethod); // 转换为C字符串并打印 const char *classNameStr = (*env)->GetStringUTFChars(env, className, NULL); const char *methodNameStr = (*env)->GetStringUTFChars(env, methodName, NULL); const char *fileNameStr = (*env)->GetStringUTFChars(env, fileName, NULL); printf("\tat %s.%s(%s:%d)\n", classNameStr, methodNameStr, fileNameStr, lineNumber); // 释放资源 (*env)->ReleaseStringUTFChars(env, className, classNameStr); (*env)->ReleaseStringUTFChars(env, methodName, methodNameStr); (*env)->ReleaseStringUTFChars(env, fileName, fileNameStr); (*env)->DeleteLocalRef(env, element); } // 释放局部引用 (*env)->DeleteLocalRef(env, stackTraceElementClass); (*env)->DeleteLocalRef(env, stackTraceArray); }
关键步骤说明
- 加载本地库:Java代码里通过
static代码块调用System.loadLibrary("hello"),加载编译好的libhello.so(Linux/macOS)或hello.dll(Windows)。 - Native方法声明:用
native关键字标记需要JNI实现的方法,方法签名要和C/C++端的函数名严格对应(格式为Java_包名_类名_方法名,包名的.换成_)。 - JNI调用Java方法:在C代码中通过JNIEnv指针的函数,先获取对应的类和方法ID,再调用
CallObjectMethod等方法执行Java端的方法,获取堆栈跟踪数组后遍历处理。
内容的提问来源于stack exchange,提问作者Nik391
相关产品推荐
相关产品推荐

