在JNI层进行性能优化和防止内存泄漏是Android NDK开发的核心挑战之一。以下是我在实践中总结的关键策略和最佳实践。
FindClass
, GetObjectClass
, NewObject
)。env->DeleteLocalRef(localRef)
):env->NewGlobalRef(obj)
env->DeleteGlobalRef(globalRef)
)。NewGlobalRef
和DeleteGlobalRef
。忘记删除是严重的内存泄漏源。env->NewWeakGlobalRef(obj)
NULL
。env->IsSameObject(weakGlobalRef, NULL) == JNI_TRUE
或 env->IsSameObject(weakGlobalRef, ...)
。env->DeleteWeakGlobalRef(weakGlobalRef)
。FindClass
, GetMethodID
, GetFieldID
涉及查找和验证,开销大。JNI_OnLoad
或首次使用类的Native方法中查找并缓存为全局引用。pthread_once
或C++11的std::call_once
确保只初始化一次。static jclass gMyClass = nullptr;
static jmethodID gMyMethodID = nullptr;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
jclass localMyClass = env->FindClass("com/example/MyClass");
if (!localMyClass) return JNI_ERR;
gMyClass = static_cast<jclass>(env->NewGlobalRef(localMyClass)); // 提升为全局引用
env->DeleteLocalRef(localMyClass); // 删除不再需要的局部引用
gMyMethodID = env->GetMethodID(gMyClass, "myMethod", "()V");
if (!gMyMethodID) return JNI_ERR;
return JNI_VERSION_1_6;
}
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return;
if (gMyClass) {
env->DeleteGlobalRef(gMyClass);
gMyClass = nullptr;
}
// jmethodID/jfieldID 本身是普通指针,不需要Delete,但关联的jclass需要
}
JNI_OnUnload
等)。std::unique_ptr
, std::shared_ptr
结合自定义删除器(如free
, delete[]
, 特定库的释放函数)能极大减少手动内存管理错误。long nativePtr
),在finalize()
或nativeDestroy()
方法中同步释放Native资源,并将指针置null
。确保Native代码检查指针有效性。Get<PrimitiveType>ArrayElements
/Release<PrimitiveType>ArrayElements
JNI_ABORT
不写回,JNI_COMMIT
写回但不释放)。Get
可能复制整个数组!Release
是必须的。GetPrimitiveArrayCritical
/ReleasePrimitiveArrayCritical
env->NewDirectByteBuffer(nativeAddress, capacity)
ByteBuffer
直接操作Native内存。Cleaner
(通过java.nio.DirectByteBuffer
的构造函数或sun.misc.Unsafe
)在GC时触发Native释放回调(PhantomReference
+ ReferenceQueue
)。GetStringChars
/ ReleaseStringChars
(UTF-16) 或 GetStringUTFChars
/ ReleaseStringUTFChars
(Modified UTF-8)。GetStringRegion
/ GetStringUTFRegion
复制部分字符串到预分配缓冲区,避免潜在复制和释放操作。JavaVM*
(保存在JNI_OnLoad
)调用 AttachCurrentThread
/ AttachCurrentThreadAsDaemon
获取JNIEnv*
。DetachCurrentThread()
。忘记Detach是常见泄漏,导致线程资源无法释放,关联的JavaThread对象泄漏。pthread_key_create
+ pthread_setspecific
+ pthread_getspecific
或 C++11 thread_local
存储JNIEnv*
(确保只attach一次)。class ScopedJniEnv {
public:
ScopedJniEnv(JavaVM* jvm) : m_jvm(jvm), m_env(nullptr), m_attached(false) {
jint ret = m_jvm->GetEnv(reinterpret_cast<void**>(&m_env), JNI_VERSION_1_6);
if (ret == JNI_EDETACHED) {
ret = m_jvm->AttachCurrentThread(&m_env, nullptr);
if (ret == JNI_OK) m_attached = true;
}
}
~ScopedJniEnv() {
if (m_attached && m_jvm) {
m_jvm->DetachCurrentThread();
}
}
JNIEnv* operator->() { return m_env; }
operator JNIEnv*() { return m_env; }
private:
JavaVM* m_jvm;
JNIEnv* m_env;
bool m_attached;
};
Call<Type>Method
、创建对象NewObject
、访问字段/方法ID),立即检查异常:env->CallVoidMethod(obj, methodID, ...);
if (env->ExceptionCheck()) {
env->ExceptionDescribe(); // 打印日志(调试用)
env->ExceptionClear(); // 清除异常(如果打算在Native处理)
// 或者 return 让异常传播到Java层
}
env->ThrowNew(env->FindClass("java/lang/Exception"), "Error message")
。Get/ReleaseArrayElements
。Critical
规则:Get/ReleasePrimitiveArrayCritical
。GetArrayRegion
/SetArrayRegion
。build.gradle
中启用:android {
defaultConfig {
externalNativeBuild {
cmake {
arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
cFlags "-fsanitize=address -fno-omit-frame-pointer"
cppFlags "-fsanitize=address -fno-omit-frame-pointer"
}
}
packagingOptions {
doNotStrip "**/*.so"
}
}
}
libc malloc
/free
)。ScopedJniEnv
)。FindClass
, GetMethodID
, NewGlobalRef
等,失败返回NULL
或抛出异常。isDestroyed()
)。NewGlobalRef
/DeleteGlobalRef
、NewWeakGlobalRef
/DeleteWeakGlobalRef
、AttachCurrentThread
/DetachCurrentThread
、Get<Type>ArrayElements
/Release<Type>ArrayElements
、GetPrimitiveArrayCritical
/ReleasePrimitiveArrayCritical
、NewDirectByteBuffer
关联的释放机制、Native指针在Java侧的释放点。assert(ptr != nullptr)
)。核心原则:JNI层的内存泄漏往往源于对JVM引用生命周期管理的疏忽(尤其是全局引用和线程附着)以及Native内存与Java对象生命周期的不同步。严格遵循引用管理规则、善用现代C++特性(智能指针、RAII)、充分利用强大的工具(ASan, Profiler, Simpleperf)进行检测和分析,是构建高性能、无泄漏JNI代码的关键。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。