目录
  1. 1. 一、JNI 在 Android 系统中的地位
    1. 1.1. 1.1 JNI 的两个角色
    2. 1.2. 1.2 JNI 相关源码路径
  2. 2. 二、静态注册 vs 动态注册
    1. 2.1. 2.1 静态注册(Static Registration)
    2. 2.2. 2.2 动态注册(Dynamic Registration)
  3. 3. 三、JNI 类型系统
    1. 3.1. 3.1 基本类型映射
    2. 3.2. 3.2 引用类型
    3. 3.3. 3.3 JNI 方法签名(Type Signature)
  4. 4. 四、JNI 引用管理:Local、Global、Weak Global
    1. 4.1. 4.1 Local Reference(本地引用)
    2. 4.2. 4.2 Global Reference(全局引用)
    3. 4.3. 4.3 Weak Global Reference(弱全局引用)
    4. 4.4. 4.4 PushLocalFrame / PopLocalFrame
  5. 5. 五、JNI 函数表结构
    1. 5.1. 5.1 JNINativeInterface 结构体
    2. 5.2. 5.2 Android 特化:JNIEnvExt
  6. 6. 六、从 Native 调用 Java
    1. 6.1. 6.1 调用流程
    2. 6.2. 6.2 FindClass 在不同线程中的区别
    3. 6.3. 6.3 方法 ID 缓存
  7. 7. 七、ART 中的 JNI 调用路径
    1. 7.1. 7.1 Java → Native 调用路径
    2. 7.2. 7.2 Native → Java 调用路径
  8. 8. 八、异常处理
    1. 8.1. 8.1 检查 JNI 异常
    2. 8.2. 8.2 从 Native 抛出 Java 异常
  9. 9. 九、CheckJNI:调试模式
    1. 9.1. 9.1 开启 CheckJNI
    2. 9.2. 9.2 CheckJNI 检查的内容
  10. 10. 十、JNI_OnLoad 和 JNI_OnUnload
    1. 10.1. 10.1 JNI_OnLoad
    2. 10.2. 10.2 JNI_OnUnload
  11. 11. 十一、核心面试题
【深入内核篇】JNI

JNI(Java Native Interface)是 Java 平台的标准编程接口,允许 Java 代码与 C/C++ 等 Native 语言交互。在 Android 系统中,JNI 不仅是应用层 NDK 开发的基础,更是整个 Android 框架运行时的核心——从 Zygote 启动到 SystemServer 初始化,从 View 渲染到 Binder 通信,底层都依赖 JNI 来桥接 Java 与 Native 世界。本文将基于 Android 11 (API 30) 的 AOSP 源码,深入剖析 JNI 的工作原理、注册机制、引用管理和 ART 集成。

一、JNI 在 Android 系统中的地位

1.1 JNI 的两个角色

JNI 在 Android 中扮演两个主要角色:

  1. Java 调用 Native:通过 native 关键字声明的方法,由 JNI 解析到对应的 C/C++ 函数实现。如 SystemProperties.get()android_os_SystemProperties.cpp 中的 native 函数。

  2. Native 调用 Java:Native 层通过 JNI 函数(FindClassGetMethodIDCallVoidMethod 等)调用 Java 层的方法。如 AMS 通过 JNI 回调 Java 层的 Binder 代理。

1.2 JNI 相关源码路径

art/runtime/jni/                    # ART JNI 实现核心
├── jni_internal.h/cpp # JNI 内部函数表
├── java_vm_ext.h/cpp # JavaVM 扩展
├── jni_env_ext.h/cpp # JNIEnv 扩展
├── check_jni.h/cpp # CheckJNI 调试模式
└── jni_entrypoints.cpp # JNI 入口点
libnativehelper/ # Android 本地辅助库
├── include/nativehelper/jni.h # JNI 辅助头文件
├── JNIHelp.cpp # JNI 辅助函数
└── JniConstants.cpp # JNI 常量缓存
frameworks/base/core/jni/ # Android 框架 JNI 层
├── AndroidRuntime.cpp # ART 启动和 JNI 注册
├── android_os_SystemProperties.cpp
├── android_view_SurfaceControl.cpp
└── ... (数百个 JNI 文件)

二、静态注册 vs 动态注册

2.1 静态注册(Static Registration)

静态注册使用 Java_<包名>_<类名>_<方法名> 的命名约定:

// frameworks/base/core/jni/android_os_SystemProperties.cpp
// 静态注册示例
extern "C" JNIEXPORT jstring JNICALL
Java_android_os_SystemProperties_getSS(
JNIEnv* env, jclass clazz, jstring keyJ, jstring defJ) {
// 从 Java 的 SystemProperties.getSS() 调用
const char* key = env->GetStringUTFChars(keyJ, NULL);
const char* def = env->GetStringUTFChars(defJ, NULL);
// ...
}

当 Java 层调用 SystemProperties.getSS() 时,ART 会自动调用 System.loadLibrary() 的 dlsym 查找这个符号名并绑定。

静态注册的优点:

  • 无需额外代码,函数名自动映射
  • 适合简单场景,开发方便

静态注册的缺点:

  • 函数名极长,不易阅读
  • 每次调用都有查找开销(缓存在 ART 中,但首次仍需要)
  • 不支持运行时取消绑定
  • 对 ProGuard 混淆敏感(类名方法名变化导致链接失败)

2.2 动态注册(Dynamic Registration)

动态注册通过 RegisterNatives 在运行时显式注册 Native 方法。Android 框架广泛使用这种方案:

// frameworks/base/core/jni/android_os_SystemProperties.cpp
// JNINativeMethod 数组:将 Java 方法映射到 Native 函数
static const JNINativeMethod gMethods[] = {
// Java方法名 签名 Native函数指针
{"native_get", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
(void*)SystemProperties_getSS},
{"native_get_int", "(Ljava/lang/String;I)I",
(void*)SystemProperties_getInt},
{"native_get_long", "(Ljava/lang/String;J)J",
(void*)SystemProperties_getLong},
{"native_get_boolean","(Ljava/lang/String;Z)Z",
(void*)SystemProperties_getBoolean},
{"native_set", "(Ljava/lang/String;Ljava/lang/String;)V",
(void*)SystemProperties_set},
{"native_add_change_callback", "()V",
(void*)SystemProperties_add_change_callback},
};

// 注册函数
int register_android_os_SystemProperties(JNIEnv* env) {
jclass clazz = env->FindClass("android/os/SystemProperties");
return env->RegisterNatives(clazz, gMethods, NELEM(gMethods));
}

动态注册的入口点通常在 onLoad.cpp 文件中:

// frameworks/base/core/jni/AndroidRuntime.cpp
extern int register_android_os_SystemProperties(JNIEnv* env);
extern int register_android_view_SurfaceControl(JNIEnv* env);
// ... 数百个 register 函数声明 ...

/*
* JNI 注册表:所有 Android 框架 native 方法在此时注册
*/
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_os_SystemProperties),
REG_JNI(register_android_view_SurfaceControl),
REG_JNI(register_android_view_SurfaceSession),
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_android_os_Binder),
// ... 依次注册所有模块
};

int AndroidRuntime::startReg(JNIEnv* env) {
// 设置 android 线程创建函数(确保每个线程附加到 JVM)
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);

// 逐一调用注册函数
for (const RegJNIRec& rec : gRegJNI) {
if (rec.mProc(env) < 0) {
return -1;
}
}
return 0;
}

这个注册过程发生在 Zygote 的 AndroidRuntime.start() 中,并且因为 Zygote 的 copy-on-write 机制,所有 fork 出的 App 进程自动继承这些注册映射。

三、JNI 类型系统

3.1 基本类型映射

JNI 定义了一套中立于底层硬件平台的类型系统:

Java 类型 JNI 类型 C/C++ 类型 字节数
boolean jboolean unsigned char 1
byte jbyte signed char 1
char jchar unsigned short 2
short jshort short 2
int jint int 4
long jlong long long 8
float jfloat float 4
double jdouble double 8
void void void N/A

3.2 引用类型

Java 类型 JNI 类型 说明
Object jobject 任意 Java 对象
Class jclass java.lang.Class
String jstring java.lang.String
Object[] jobjectArray 对象数组
boolean[] jbooleanArray boolean 数组
byte[] jbyteArray byte 数组
char[] jcharArray char 数组
short[] jshortArray short 数组
int[] jintArray int 数组
long[] jlongArray long 数组
float[] jfloatArray float 数组
double[] jdoubleArray double 数组
Throwable jthrowable 异常对象

3.3 JNI 方法签名(Type Signature)

JNI 使用紧凑的字符编码来表示 Java 类型签名:

Java 类型签名 JNI 签名 含义
Z boolean 布尔
B byte 字节
C char 字符
S short 短整型
I int 整型
J long 长整型
F float 单精度浮点
D double 双精度浮点
V void 空返回
LclassName; 对象 Ljava/lang/String;
[type type[] 如一维 int 数组 [I
(arg)ret 方法 (ILjava/lang/String;)V

示例:

// Java: public native String getMessage(int code, byte[] data, String tag);
// JNI签名: "(I[BLjava/lang/String;)Ljava/lang/String;"

// Java: public native void onEvent(long timestamp, boolean success);
// JNI签名: "(JZ)V"

四、JNI 引用管理:Local、Global、Weak Global

JNI 引用的管理是 JNI 编程中最容易出错的领域。理解这三种引用的区别和限制至关重要。

4.1 Local Reference(本地引用)

本地引用在 native 方法执行期间有效,方法返回后自动释放。

// 本地引用示例
jstring local_str = env->NewStringUTF("hello");
// local_str 在当前 JNI 方法返回后自动释放

// 如果需要提前释放:
env->DeleteLocalRef(local_str);

关键限制:每个 JNI 方法调用中,本地引用表的默认容量为 512(Android 实现)。超过此限制会触发 JNI ERROR (app bug): local reference table overflow 崩溃。

// ❌ 错误:在循环中创建大量本地引用而不释放
for (int i = 0; i < 1000; i++) {
jobject obj = env->FindClass("java/lang/Object"); // 泄漏!
// 每次 FindClass 返回一个新的本地引用
}
// 最终超过 512 限制 → 崩溃

// ✅ 正确做法:
for (int i = 0; i < 1000; i++) {
jobject obj = env->FindClass("java/lang/Object");
// 使用完后立即释放
env->DeleteLocalRef(obj);
}

ART 中的本地引用表实现:

// art/runtime/indirect_reference_table.h
class IndirectReferenceTable {
public:
static constexpr size_t kInitialCapacity = 512; // 初始 512 个 slot
// ...
private:
// segment state push/pop 机制允许嵌套的 JNI 调用扩展容量
// 但直接在一个调用中超过 512 仍然会崩溃
};

4.2 Global Reference(全局引用)

全局引用在显式删除前一直有效,不受 native 方法返回的影响:

jclass g_my_class = nullptr;

JNIEXPORT void JNICALL Java_MyClass_init(JNIEnv* env, jclass clazz) {
// 创建全局引用
g_my_class = (jclass)env->NewGlobalRef(clazz);
// g_my_class 在整个进程生命周期中有效
}

JNIEXPORT void JNICALL Java_MyClass_cleanup(JNIEnv* env, jclass clazz) {
// 显式删除全局引用
if (g_my_class != nullptr) {
env->DeleteGlobalRef(g_my_class);
g_my_class = nullptr;
}
}

全局引用表的特点:

  • 没有固定容量限制(受进程内存限制)
  • 全局引用阻止 GC 回收被引用的对象 → 可能导致内存泄漏
  • 使用 NewGlobalRef() 创建的每个引用都需要对应的 DeleteGlobalRef()

4.3 Weak Global Reference(弱全局引用)

弱全局引用不阻止 GC 回收对象,但需要在使用前检查对象是否存活:

jclass g_weak_class = nullptr;

JNIEXPORT void JNICALL Java_MyClass_init(JNIEnv* env, jclass clazz) {
g_weak_class = (jclass)env->NewWeakGlobalRef(clazz);
}

JNIEXPORT void JNICALL Java_MyClass_use(JNIEnv* env, jclass clazz) {
if (env->IsSameObject(g_weak_class, nullptr)) {
// 对象已被 GC 回收
return;
}
// 可以使用 g_weak_class(注意:在 IsSameObject 和使用之间,
// GC 可能还在另一个线程运行,所以此处推荐使用 NewLocalRef 获取强引用)
}

4.4 PushLocalFrame / PopLocalFrame

为了管理嵌套调用中的本地引用,JNI 提供了本地栈帧机制:

JNIEXPORT void JNICALL Java_MyClass_processBatch(JNIEnv* env, jclass clazz,
jobjectArray items) {
// 推入新的本地引用栈帧(容量为 256)
if (env->PushLocalFrame(256) < 0) {
return; // 内存不足
}

jsize len = env->GetArrayLength(items);
for (jsize i = 0; i < len; i++) {
jobject item = env->GetObjectArrayElement(items, i);
// 处理 item...(本地引用在栈帧内自动管理)
// 不需要手动 DeleteLocalRef
env->DeleteLocalRef(item);
}

// 弹出栈帧:自动释放此帧内所有本地引用
env->PopLocalFrame(nullptr);
}

五、JNI 函数表结构

5.1 JNINativeInterface 结构体

JNIEnv* 是一个指向 JNINativeInterface 函数表指针的指针。这个表包含大约 230 个函数指针:

// art/runtime/jni/jni_internal.h
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;

jint (*GetVersion)(JNIEnv *);
jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*, jsize);
jclass (*FindClass)(JNIEnv*, const char*);
// ... 230+ 函数指针 ...
jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*, jint);
jint (*UnregisterNatives)(JNIEnv*, jclass);
};

// JNIEnv 的定义:
typedef const struct JNINativeInterface* JNIEnv;

调用 JNI 函数的过程:

// Java_com_example_MyClass_nativeMethod(JNIEnv* env, ...) {
// env->FindClass(...) 实际上是:
// (*env)->FindClass(env, "java/lang/String")
// }

5.2 Android 特化:JNIEnvExt

Android 在标准 JNINativeInterface 的基础上扩展了 JNIEnvExt,增加了一些 Android 特有的功能:

// art/runtime/jni/jni_env_ext.h
struct JNIEnvExt : public JNIEnv {
// 扩展字段
Thread* self; // ART 线程指针
JavaVMExt* vm; // JavaVM 扩展指针
bool critical; // 是否在关键区域中
bool force_copy; // 是否强制复制数组
// CheckJNI 相关
bool check_jni; // 是否启用 CheckJNI
std::vector<CheckJniRec>* check_jni_trace;
// 引用管理
uint32_t local_ref_cookie; // 本地引用 cookie
IndirectReferenceTable locals; // 本地引用表
};

六、从 Native 调用 Java

6.1 调用流程

从 C/C++ 代码调用 Java 方法需要以下步骤:

// 示例:调用 Java 对象的 void onResult(boolean success, String message) 方法
void callOnResult(JNIEnv* env, jobject callbackObj, bool success, const char* msg) {
// 1. 获取 Java 类
jclass clazz = env->GetObjectClass(callbackObj);

// 2. 获取方法 ID
jmethodID methodId = env->GetMethodID(clazz, "onResult", "(ZLjava/lang/String;)V");
if (methodId == nullptr) {
// 方法未找到(可能是混淆后名称改变)
return;
}

// 3. 构造参数
jstring jmsg = env->NewStringUTF(msg);

// 4. 调用方法
env->CallVoidMethod(callbackObj, methodId, (jboolean)success, jmsg);

// 5. 清理本地引用
env->DeleteLocalRef(jmsg);
env->DeleteLocalRef(clazz);
}

6.2 FindClass 在不同线程中的区别

// 普通 JNI 方法中(由 Java 调用进入):可以直接 FindClass
JNIEXPORT void JNICALL Java_MyClass_foo(JNIEnv* env, jobject thiz) {
jclass clazz = env->FindClass("java/lang/String"); // ✅ 成功
}

// Native 线程中(自己创建的 pthread):
void* native_thread_func(void* arg) {
JavaVM* jvm = (JavaVM*)arg;
JNIEnv* env;

// 1. 必须先将线程附加到 JVM
jvm->AttachCurrentThread(&env, nullptr);

// 2. 现在可以调用 FindClass
jclass clazz = env->FindClass("java/lang/String"); // ✅ 现在可以了

// 3. 如果线程长时间不再使用,需要 detach
jvm->DetachCurrentThread();
return nullptr;
}

FindClass 使用调用者的 ClassLoader 来查找类。在 native 线程中,初始状态下没有 ClassLoader,所以 FindClass 只能找到 bootstrap classpath 中的类(如 java.lang.*)。框架类(android.*)需要从 ClassLoader 获取:

// 获取 Android 框架类的方法
jclass findAndroidClass(JNIEnv* env, const char* name) {
// 方案1:从已知对象获取 ClassLoader
jclass classClassLoader = env->FindClass("java/lang/ClassLoader");
jmethodID loadClass = env->GetMethodID(classClassLoader, "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
// 需要先获取 system ClassLoader...

// 方案2:使用 AndroidRuntime 的辅助函数
// 在 libnativehelper 中提供了便捷方法
}

6.3 方法 ID 缓存

GetMethodIDGetFieldID 的性能开销较大,因为它们需要进行字符串哈希和比较。最佳实践是在类加载时缓存这些 ID:

// 全局缓存
static jmethodID g_onResultMethodId = nullptr;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
vm->GetEnv((void**)&env, JNI_VERSION_1_6);

// 提前缓存方法 ID
jclass clazz = env->FindClass("com/example/Callback");
g_onResultMethodId = env->GetMethodID(clazz, "onResult", "(ZLjava/lang/String;)V");
// g_onResultMethodId 在进程生命周期内有效(方法 ID 不会被 GC 移动)

return JNI_VERSION_1_6;
}

七、ART 中的 JNI 调用路径

7.1 Java → Native 调用路径

当一个 Java native 方法被调用时,ART 的执行流程:

Java: MyClass.myNativeMethod()
→ ArtMethod::Invoke() // art/runtime/art_method.cc
→ artQuickGenericJniTrampoline() // art/runtime/entrypoints/quick/quick_jni_entrypoints.cc
→ JniMethodStart() // 准备 JNI 环境
→ native_func(env, thiz, args...) // 调用实际的 native 函数
→ JniMethodEnd() // 清理 JNI 环境
→ 返回 Java 层

具体实现:

// art/runtime/entrypoints/quick/quick_jni_entrypoints.cc
// 快速 JNI 入口(使用汇编优化)
extern "C" uint64_t artQuickGenericJniTrampoline(
Thread* self, ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_) {
ArtMethod* method = *sp;
const void* native_code = method->GetEntryPointFromJni();
JNIEnv* env = self->GetJniEnv();

// 解析参数(从 Java 调用栈中取出参数并转换为 JNI 类型)
// 调用 native_code(env, receiver_or_class, args...)
// 检查是否有 pending exception
// 返回结果
}

7.2 Native → Java 调用路径

C: env->CallVoidMethod(obj, methodId, args)
→ JNIEnvExt::CallVoidMethod() // art/runtime/jni/jni_env_ext.cc
→ ScopedObjectAccess::Decode() // 解码 jobject → mirror::Object*
→ art::InvokeVirtualOrInterfaceWithJValues() // art/runtime/reflection.cc (间接)
→ 进入 ART 解释器 / AOT 编译代码

关键性能开销:

  • FindClass:类名字符串查找,涉及 class table 哈希查找
  • GetMethodID:方法名和签名查找,涉及 ArtMethod 哈希查找
  • Call*Method:参数类型转换(JNI types → ART internal types)

八、异常处理

8.1 检查 JNI 异常

大多数 JNI 函数在出错时不会立即 crash,而是在 JNIEnv 中设置 pending exception。Native 代码必须在调用可能失败的 JNI 函数后立即检查异常:

JNIEXPORT void JNICALL Java_MyClass_callJava(JNIEnv* env, jobject thiz) {
jclass clazz = env->FindClass("com/example/NonExistentClass");
if (env->ExceptionCheck()) {
// 类未找到!清理异常
env->ExceptionClear();
// 回退处理...
return;
}

jmethodID method = env->GetMethodID(clazz, "someMethod", "()V");
if (env->ExceptionCheck()) {
env->ExceptionClear();
return;
}

env->CallVoidMethod(thiz, method);
if (env->ExceptionCheck()) {
// Java 方法自身抛出了异常
env->ExceptionDescribe(); // 输出异常信息到 logcat
env->ExceptionClear();
}
}

8.2 从 Native 抛出 Java 异常

JNIEXPORT void JNICALL Java_MyClass_validateInput(JNIEnv* env, jobject thiz,
jstring input) {
if (input == nullptr) {
jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
env->ThrowNew(clazz, "Input cannot be null");
return; // 即使调用了 ThrowNew,native 函数仍然返回
}
// 正常处理...
}

九、CheckJNI:调试模式

9.1 开启 CheckJNI

CheckJNI 是 ART 的一个严格检查模式,会在 JNI 调用时进行额外验证:

# 对特定应用开启
adb shell setprop dalvik.vm.checkjni true

# 对所有应用开启(只对 eng/userdebug build)
adb shell stop
adb shell setprop dalvik.vm.checkjni true
adb shell start

# 或在应用 manifest 中声明
<application android:debuggable="true" ...>

9.2 CheckJNI 检查的内容

// art/runtime/jni/check_jni.cpp
// CheckJNI 额外验证:
// 1. 检查 jobject 类型是否正确(如不应把 jclass 当作 jobject 传递)
// 2. 检查数组索引越界
// 3. 检查是否在主线程做耗时 JNI 调用
// 4. 检查是否在 pending exception 状态下继续调用 JNI 函数
// 5. 验证 jfieldID 和 jmethodID 的有效性
// 6. 检查 DeleteLocalRef 是否释放了不应该释放的引用
// 7. 验证 GetStringUTFChars 后是否调用了 ReleaseStringUTFChars
// 8. 检查是否在错误的线程使用 JNIEnv

static void CheckMethodAndArgs(JNIEnv* env, jclass c, jmethodID mid) {
// 验证 method ID 属于该类
// 验证参数类型是否匹配
// 验证对象是否可访问
// ...
}

开启 CheckJNI 的性能开销大约是 5-10%,主要用在开发阶段检测 JNI 使用错误。

十、JNI_OnLoad 和 JNI_OnUnload

10.1 JNI_OnLoad

System.loadLibrary() 加载一个 native 库时,ART 会查找并调用库中的 JNI_OnLoad 函数。这是执行初始化(如动态注册 native 方法)的最佳时机:

// 典型的 JNI_OnLoad 实现
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = nullptr;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}

// 动态注册所有 native 方法
if (!registerNativeMethods(env)) {
return JNI_ERR;
}

// 缓存类引用和方法 ID
cacheClassReferences(env);

return JNI_VERSION_1_6;
}

static bool registerNativeMethods(JNIEnv* env) {
jclass clazz = env->FindClass("com/example/MyLib");
if (clazz == nullptr) return false;

static const JNINativeMethod methods[] = {
{"nativeInit", "()Z", (void*)nativeInit},
{"nativeProcess", "([B)I", (void*)nativeProcess},
{"nativeCleanup", "()V", (void*)nativeCleanup},
};

return env->RegisterNatives(clazz, methods, NELEM(methods)) == JNI_OK;
}

10.2 JNI_OnUnload

当 ClassLoader 被 GC 回收时,ART 会调用 JNI_OnUnload(在 Android 中很少发生,因为大多数 native 库与进程生命周期绑定):

void JNI_OnUnload(JavaVM* vm, void* reserved) {
// 释放全局引用
// 清理 native 资源
}

十一、核心面试题

Q1:静态注册和动态注册的内部查找机制有什么不同?为什么 Android 框架倾向于使用动态注册?

答:静态注册在第一次调用时由 ART 通过 dlsym 查找 Java_<包名>_<类名>_<方法名> 符号,解析成功后缓存在 ArtMethod 结构中。动态注册通过 RegisterNatives 直接建立 ArtMethod → native 函数指针的映射,跳过了符号查找过程。Android 框架偏好动态注册的原因:(1) 数百个 JNI 模块,静态注册的函数名极长且易冲突;(2) 启动性能:所有方法在 Zygote 启动阶段一次性注册完毕,避免了懒加载的累计开销;(3) ProGuard/R8 混淆兼容性:类名和方法名可能被混淆,但动态注册使用的是固定的方法名字符串;(4) 更灵活的版本管理:可以条件判断不同 API level 注册不同方法。

Q2:本地引用表溢出(local reference table overflow)的根因是什么?如何诊断和修复?

答:根因是 JNI 方法在一个调用栈帧中分配的本地引用数量超过了 512 的限制(ART 默认值)。ART 在每次 CallVoidMethod/FindClass/NewObject 等操作时,会在 IndirectReferenceTable 中插入一个新条目。这些引用在 JNI 方法返回时自动释放,但如果在方法内循环创建大量引用而不手动 DeleteLocalRef,就会在方法返回前溢出。这可以视为一种限定范围内的内存泄漏。诊断方法:开启 CheckJNI (dalvik.vm.checkjni=true) 会在溢出时输出详细 trace。修复方法:(1) 在循环内调用 DeleteLocalRef;(2) 使用 PushLocalFrame/PopLocalFrame 管理生命周期;(3) 将大循环拆分为多个 JNI 调用。

Q3:GetStringUTFCharsGetStringCritical 的区别是什么?为什么后者需要更谨慎地使用?

答:GetStringUTFChars 返回 UTF-8 编码的字符串副本(可能有内存分配和转换开销),调用期间允许 GC 运行,JVM 可能会暂停 native 代码执行。GetStringCritical 直接返回指向 Java String 内部缓冲区的指针(零拷贝),但其约束严格得多:(1) “critical region” 内不能调用任何可能阻塞或分配内存的 JNI 函数(包括 FindClassNewStringUTF 等);(2) critical region 应尽量短;(3) 不能嵌套 critical region。GetStringCritical 的主要优势是避免内存分配和 UTF-8 转换开销,适用于简单快速的字符检测场景。在 ART 实现中,GetStringCritical 实际上仍然会进行 UTF-8 转换(因为 ART 内部使用 Modified UTF-8),所以性能差异不像 HotSpot 那么明显。

Q4:为什么 native 线程在调用 FindClass 之前必须调用 AttachCurrentThread?ART 在 attach 过程中做了什么?

答:ART 的 JNIEnv 是线程局部的(thread-local),通过 art::Thread 对象维护。一个 native(pthread)线程在创建时没有被 ART 识别——它没有关联的 art::Thread 对象、没有 JNIEnv、没有 ClassLoader 上下文。AttachCurrentThread 会:(1) 创建一个 art::Thread 对象;(2) 分配一个 JNIEnvExt(包括本地引用表等);(3) 将线程注册到 ART 的 GC 系统中,这样 GC 才能在此线程的栈上遍历 JNI 本地引用;(4) 设置线程的 ClassLoader 为 null(bootstrap classpath only),因此只能找到 java.lang.* 等核心类。如果需要访问框架类,需要手动通过已知对象获取 ClassLoader 并用其 loadClass 方法。

Q5:JNI_OnLoad 中缓存 jmethodID 是安全的吗?如果 ClassLoader 卸载该类重新加载,缓存的 ID 会失效吗?

答:在 Android 的默认 ClassLoader 策略下,一个类一旦被加载就不会被卸载(除了极端的内存压力情况,且即便是 ART 的类卸载也极其罕见)。jmethodID 本质上是 ArtMethod* 指针,只要类本身不被 GC 回收,这个指针就持续有效。因此缓存 jmethodID 在 Android 中是安全且推荐的做法。但如果库被多个 ClassLoader 加载(如插件化框架),同一个类可能有多个 ClassLoader 副本,此时缓存的 ID 只对应一个 ClassLoader 中的版本。另外需要注意:对于 jclass 不应该缓存普通的本地引用,应该使用 NewGlobalRef 将其提升为全局引用。

AOSP 核心路径参考:

  • art/runtime/jni/jni_internal.h — JNINativeInterface 函数表
  • art/runtime/jni/jni_env_ext.h — JNIEnvExt 扩展定义
  • art/runtime/jni/check_jni.cpp — CheckJNI 实现
  • art/runtime/indirect_reference_table.h — 引用表实现
  • art/runtime/entrypoints/quick/quick_jni_entrypoints.cc — JNI 快速入口
  • art/runtime/entrypoints/jni/jni_entrypoints.cc — JNI 通用入口
  • libnativehelper/include/nativehelper/jni.h — JNI 辅助宏和方法
  • frameworks/base/core/jni/AndroidRuntime.cpp — Android JNI 注册框架
  • frameworks/base/core/jni/android_os_SystemProperties.cpp — 动态注册范例
打赏
  • 微信
  • 支付宝

评论