一:内存管理
在 JNI 中,内存管理是一个重要的主题,尤其是在处理 Java 和 C++ 之间的对象时。以下是一些关键点和最佳实践,以确保有效的内存管理。
1. 局部引用管理
- 局部引用:在 JNI 中,调用 Java 方法时会创建局部引用。这些引用在方法返回后会被自动释放,但在长时间运行的本地方法中,可能会导致局部引用表溢出。
- 手动删除:使用
DeleteLocalRef
手动删除不再需要的局部引用。
jobject localRef = env->NewObject(...);
// 使用 localRef
env->DeleteLocalRef(localRef); // 手动删除
2. 全局引用
- 全局引用:如果需要在多个 JNI 调用之间共享对象,可以创建全局引用。全局引用在整个应用程序生命周期内有效,直到显式删除。
- 创建全局引用:使用
NewGlobalRef
创建全局引用。 - 删除全局引用:使用
DeleteGlobalRef
删除全局引用。
jobject globalRef = env->NewGlobalRef(localRef);
// 使用 globalRef
env->DeleteGlobalRef(globalRef); // 手动删除
3. 字符串和数组管理
- 字符串:使用
GetStringUTFChars
获取 C 字符串时,确保在使用后调用ReleaseStringUTFChars
释放。
jstring jstr = ...;
const char *cstr = env->GetStringUTFChars(jstr, nullptr);
// 使用 cstr
env->ReleaseStringUTFChars(jstr, cstr); // 释放
- 数组:在处理 Java 数组时,使用
Get<PrimitiveType>ArrayElements
获取元素,并在使用后调用相应的释放函数。
jintArray intArray = ...;
jint *elements = env->GetIntArrayElements(intArray, nullptr);
// 使用 elements
env->ReleaseIntArrayElements(intArray, elements, 0); // 释放
4. 避免内存泄漏
- 检查返回值:在调用 JNI 函数时,检查返回值是否为
nullptr
,以避免对空指针的操作。 - 适时释放:确保在不再需要对象时及时释放引用,避免内存泄漏。
5. 使用 CallNonvirtualVoidMethod
当需要调用父类的方法时,可以使用 CallNonvirtualVoidMethod
。以下是如何使用它的示例:
示例代码
假设有一个 Java 类 Parent
和一个子类 Child
,我们希望从 C++ 中调用 Parent
的方法。
// Parent.java
package com.example.yourapp;
public class Parent {
public void greet() {
System.out.println("Hello from Parent!");
}
}
// Child.java
package com.example.yourapp;
public class Child extends Parent {
public void greet() {
System.out.println("Hello from Child!");
}
}
C++ 代码
#include <jni.h>
#include <android/log.h>
#define LOG_TAG "NativeExample"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
extern "C" JNIEXPORT void JNICALL
Java_com_example_yourapp_MainActivity_callParentGreet(JNIEnv *env, jobject childObj) {
// 获取 Parent 类的引用
jclass parentClass = env->FindClass("com/example/yourapp/Parent");
if (parentClass == nullptr) {
LOGI("Failed to find Parent class");
return;
}
// 获取 greet 方法的 ID
jmethodID greetMethod = env->GetMethodID(parentClass, "greet", "()V");
if (greetMethod == nullptr) {
LOGI("Failed to find greet method");
return;
}
// 调用 Parent 的 greet 方法
env->CallNonvirtualVoidMethod(childObj, parentClass, greetMethod);
// 释放局部引用
env->DeleteLocalRef(parentClass);
}
代码解释
- 获取类引用:使用
FindClass
获取父类Parent
的引用。 - 获取方法 ID:使用
GetMethodID
获取greet
方法的 ID。 - 调用父类方法:使用
CallNonvirtualVoidMethod
调用父类的greet
方法。 - 释放局部引用:确保释放所有局部引用以避免内存泄漏。
1.局部引用
大多数的jni函数,调用以后返回的结果都是局部引用
因此env→NewLocalRef基本不用
一个函数内的局部引用数量是有限的,在早期的安卓系统中,体现的更未明显,当函数内需要大量局部引用时,比如大循环中,最好及时删除不用的局部引用,可以使用env→DeleLocalRef来删除局部引用.
局部引用是 JNI 中的重要概念,它们用于管理从 Java 代码中创建的对象的生命周期。理解局部引用的工作原理对于确保在 JNI 中有效的内存管理至关重要。
什么是局部引用?
局部引用是在 JNI 方法中创建的对象引用,这些引用在方法执行期间有效。当 JNI 方法返回时,这些局部引用会被自动释放。局部引用的作用范围仅限于创建它们的 JNI 方法。
创建局部引用
局部引用通常通过 JNI 函数创建,例如:
NewObject
:创建一个新的 Java 对象。NewStringUTF
:创建一个新的字符串对象。GetObjectArrayElement
:从对象数组中获取元素。
示例
jclass myClass = env->FindClass("com/example/MyClass");
jmethodID constructor = env->GetMethodID(myClass, "<init>", "()V");
jobject myObject = env->NewObject(myClass, constructor);
在这个例子中,myObject
是一个局部引用,指向新创建的 Java 对象。
管理局部引用
虽然局部引用在方法返回时会被自动释放,但在某些情况下(例如长时间运行的本地方法),可能会导致局部引用表溢出。因此,适当管理局部引用非常重要。
1. 手动删除局部引用
在不再需要某个局部引用时,可以使用 DeleteLocalRef
手动删除它,以释放内存:
env->DeleteLocalRef(myObject);
2. 避免局部引用溢出
如果在一个 JNI 方法中创建了大量的局部引用,可能会导致局部引用表溢出。可以使用以下方法来避免这种情况:
- 定期删除局部引用:在循环中创建局部引用时,定期调用
DeleteLocalRef
。 - 使用
PushLocalFrame
和PopLocalFrame
:这两个方法允许你在局部引用帧中管理局部引用的生命周期。
env->PushLocalFrame(16); // 创建一个局部引用帧,最多可以存储 16 个局部引用
jobject localRef = env->NewObject(...);
// 使用 localRef
env->PopLocalFrame(nullptr); // 释放局部引用帧中的所有引用
例子:局部引用的使用
以下是一个简单的例子,展示如何在 JNI 中使用局部引用:
extern "C" JNIEXPORT void JNICALL
Java_com_example_yourapp_MainActivity_exampleMethod(JNIEnv *env, jobject obj) {
jclass stringClass = env->FindClass("java/lang/String");
if (stringClass == nullptr) {
return; // 处理错误
}
jmethodID constructor = env->GetMethodID(stringClass, "<init>", "(Ljava/lang/String;)V");
jstring jstr = env->NewStringUTF("Hello from JNI!");
jobject myStringObject = env->NewObject(stringClass, constructor, jstr);
// 使用 myStringObject
// ...
// 释放局部引用
env->DeleteLocalRef(jstr);
env->DeleteLocalRef(myStringObject);
env->DeleteLocalRef(stringClass);
}
总结
局部引用是 JNI 中管理 Java 对象的基本机制。正确使用和管理局部引用可以避免内存泄漏和局部引用表溢出。通过手动删除不再需要的引用和使用局部引用帧,可以有效地控制内存使用。
2.局部引用相关的其他函数
env→EnsureLocalcapacity(num),判断是否有足够的局部引用可以使用,足够则返回0,需要大量使用局部引用时,手动删除太麻烦,可以使用一下俩个函数来批量管理局部引用.
env→PushLocalFrame(num)
env→PopLocalFrame(nullptr)
在 JNI 中,除了基本的局部引用管理函数 NewLocalRef
和 DeleteLocalRef
外,还有一些其他相关的函数,可以帮助管理和优化局部引用的使用。以下是一些重要的局部引用相关函数及其用途:
1. PushLocalFrame
和 PopLocalFrame
PushLocalFrame
:创建一个新的局部引用帧,可以在该帧中存储局部引用。局部引用将在帧被弹出时自动释放。PopLocalFrame
:释放局部引用帧中的所有局部引用。
示例
env->PushLocalFrame(10); // 创建一个局部引用帧,最多可以存储 10 个局部引用
jobject localRef1 = env->NewObject(...);
jobject localRef2 = env->NewObject(...);
// 使用 localRef1 和 localRef2
env->PopLocalFrame(nullptr); // 释放局部引用帧中的所有引用
2. NewLocalRef
NewLocalRef
:创建一个新的局部引用,指向现有的对象。它可以用于将全局引用或其他局部引用转换为局部引用。
示例
jobject globalRef = env->NewGlobalRef(someObject);
jobject localRef = env->NewLocalRef(globalRef); // 将全局引用转换为局部引用
// 使用 localRef
env->DeleteLocalRef(localRef);
env->DeleteGlobalRef(globalRef); // 释放全局引用
3. GetObjectArrayElement
GetObjectArrayElement
:从 Java 对象数组中获取元素,返回的对象是局部引用。
示例
jobjectArray objectArray = ...; // 假设这是一个有效的 jobjectArray
jobject element = env->GetObjectArrayElement(objectArray, index);
// 使用 element
env->DeleteLocalRef(element); // 释放局部引用
4. SetObjectArrayElement
SetObjectArrayElement
:将一个对象设置到 Java 对象数组中的指定索引。传入的对象可以是局部引用。
示例
jobjectArray objectArray = ...; // 假设这是一个有效的 jobjectArray
jobject newElement = env->NewObject(...); // 创建一个新的对象
env->SetObjectArrayElement(objectArray, index, newElement);
env->DeleteLocalRef(newElement); // 释放局部引用
5. CallObjectMethod
CallObjectMethod
:调用对象的方法并返回一个对象,返回的对象是局部引用。
示例
jobject result = env->CallObjectMethod(someObject, methodID);
// 使用 result
env->DeleteLocalRef(result); // 释放局部引用
6. GetMethodID
和 GetFieldID
GetMethodID
和GetFieldID
:虽然这些函数本身不创建局部引用,但它们返回的方法和字段 ID 可以用于后续操作,调用这些方法或访问字段时会产生局部引用。
示例
jclass clazz = env->FindClass("com/example/MyClass");
jmethodID methodID = env->GetMethodID(clazz, "myMethod", "()V");
jfieldID fieldID = env->GetFieldID(clazz, "myField", "Ljava/lang/String;");
7. GetStringUTFChars
和 ReleaseStringUTFChars
GetStringUTFChars
:获取 Java 字符串的 C 字符串表示,返回的指针需要在使用后释放。ReleaseStringUTFChars
:释放通过GetStringUTFChars
获取的 C 字符串。
示例
jstring jstr = ...;
const char *cstr = env->GetStringUTFChars(jstr, nullptr);
// 使用 cstr
env->ReleaseStringUTFChars(jstr, cstr); // 释放
3.全局引用
在 JNI 中,全局引用是一种特殊的引用类型,用于在 C++ 代码中持有 Java 对象的引用。与局部引用不同,全局引用在 JNI 方法返回后仍然有效,直到显式删除。全局引用的主要用途是允许跨多个 JNI 方法或线程使用 Java 对象。
什么是全局引用?
全局引用是在 JNI 中创建的对象引用,可以在整个应用程序生命周期内使用。它们的主要特性包括:
- 持久性:全局引用在创建后不会自动释放,直到调用
DeleteGlobalRef
显式删除。 - 跨方法和线程:全局引用可以在不同的 JNI 方法和线程之间共享。
创建全局引用
要创建全局引用,可以使用 NewGlobalRef
函数。以下是创建全局引用的示例:
jobject localRef = env->NewObject(...); // 创建一个局部引用
jobject globalRef = env->NewGlobalRef(localRef); // 创建全局引用
env->DeleteLocalRef(localRef); // 可以安全地删除局部引用
删除全局引用
全局引用在不再需要时必须显式删除,以避免内存泄漏。使用 DeleteGlobalRef
来删除全局引用:
env->DeleteGlobalRef(globalRef); // 删除全局引用
使用全局引用
全局引用可以在 JNI 方法中安全地使用,以下是一个使用全局引用的示例:
extern "C" JNIEXPORT void JNICALL
Java_com_example_yourapp_MainActivity_useGlobalRef(JNIEnv *env, jobject obj) {
// 创建一个局部引用并转换为全局引用
jclass myClass = env->FindClass("com/example/MyClass");
jmethodID constructor = env->GetMethodID(myClass, "<init>", "()V");
jobject localRef = env->NewObject(myClass, constructor);
jobject globalRef = env->NewGlobalRef(localRef);
env->DeleteLocalRef(localRef); // 删除局部引用
// 使用全局引用
jmethodID someMethod = env->GetMethodID(myClass, "someMethod", "()V");
env->CallVoidMethod(globalRef, someMethod);
// 删除全局引用
env->DeleteGlobalRef(globalRef);
}
全局引用的注意事项
- 内存管理:全局引用不会自动释放,因此必须在不再需要时手动删除,以避免内存泄漏。
- 线程安全:全局引用可以在多个线程中使用,但需要确保在多线程环境中对全局引用的访问是线程安全的。
- 局部引用与全局引用的选择:如果只需在单个 JNI 方法中使用对象,使用局部引用即可。如果需要在多个方法或线程之间共享对象,则应使用全局引用。
例子:全局引用管理
以下是一个完整的示例,展示如何在 JNI 中创建和使用全局引用:
#include <jni.h>
#include <android/log.h>
#define LOG_TAG "NativeExample"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
extern "C" JNIEXPORT void JNICALL
Java_com_example_yourapp_MainActivity_exampleMethod(JNIEnv *env, jobject obj) {
// 创建一个局部引用
jclass myClass = env->FindClass("com/example/MyClass");
jmethodID constructor = env->GetMethodID(myClass, "<init>", "()V");
jobject localRef = env->NewObject(myClass, constructor);
// 创建全局引用
jobject globalRef = env->NewGlobalRef(localRef);
env->DeleteLocalRef(localRef); // 删除局部引用
// 调用方法
jmethodID someMethod = env->GetMethodID(myClass, "someMethod", "()V");
env->CallVoidMethod(globalRef, someMethod);
// 删除全局引用
env->DeleteGlobalRef(globalRef);
}
总结
全局引用在 JNI 中提供了一种持久的方式来管理 Java 对象,允许跨多个 JNI 方法和线程使用这些对象。正确管理全局引用至关重要,以避免内存泄漏和确保应用程序的稳定性。通过适当的创建和删除全局引用,可以有效地在 C++ 和 Java 之间进行交互。
4.弱全局引用
在 JNI 中,全局引用和弱全局引用是用于管理 Java 对象生命周期的两种重要引用类型。它们在内存管理、性能优化和避免内存泄漏方面具有不同的特性和使用场景。
全局引用
全局引用是 JNI 中一种特殊的引用类型,它可以在多个 JNI 调用之间共享,并在整个应用程序生命周期内有效。全局引用不会被垃圾回收器自动释放,直到显式删除。
创建全局引用
使用 NewGlobalRef
创建全局引用:
jobject globalRef = env->NewGlobalRef(localRef);
删除全局引用
使用 DeleteGlobalRef
删除全局引用:
env->DeleteGlobalRef(globalRef);
弱全局引用
弱全局引用(Weak Global Reference)是一种特殊的引用类型,它允许程序在不阻止对象被垃圾回收的情况下引用 Java 对象。弱全局引用在 Java 中的生命周期与普通全局引用不同,垃圾回收器可以在需要时回收弱全局引用指向的对象。
创建弱全局引用
使用 NewWeakGlobalRef
创建弱全局引用:
jobject weakGlobalRef = env->NewWeakGlobalRef(localRef);
删除弱全局引用
使用 DeleteWeakGlobalRef
删除弱全局引用:
env->DeleteWeakGlobalRef(weakGlobalRef);
使用弱全局引用的场景
- 避免内存泄漏:在某些情况下,使用弱全局引用可以防止强引用导致的内存泄漏。例如,当你想要缓存对象,但又不希望阻止它们被垃圾回收时,可以使用弱全局引用。
- 缓存对象:在某些情况下,可以使用弱全局引用缓存对象,以便在需要时访问,但不阻止对象被回收。
- 事件处理:在事件处理或回调机制中,使用弱全局引用可以避免循环引用的问题。
检查弱全局引用的有效性
由于弱全局引用在对象被垃圾回收后会变为 nullptr
,因此在使用弱全局引用之前,应该检查它是否仍然有效。可以使用 IsSameObject
来检查弱全局引用是否指向一个有效的对象。
示例代码
以下是一个使用弱全局引用的示例:
extern "C" JNIEXPORT void JNICALL
Java_com_example_yourapp_MainActivity_exampleMethod(JNIEnv *env, jobject obj) {
// 创建一个局部引用
jobject localRef = env->NewObject(...);
// 创建弱全局引用
jobject weakGlobalRef = env->NewWeakGlobalRef(localRef);
// 删除局部引用
env->DeleteLocalRef(localRef);
// 检查弱全局引用是否有效
if (weakGlobalRef != nullptr) {
// 使用 weakGlobalRef
// ...
} else {
// 对象已被垃圾回收
}
// 删除弱全局引用
env->DeleteWeakGlobalRef(weakGlobalRef);
}
总结
弱全局引用在 JNI 中提供了一种灵活的方式来管理 Java 对象的生命周期。它们允许程序在不阻止对象被垃圾回收的情况下引用对象,从而帮助避免内存泄漏和循环引用。使用弱全局引用时,确保在使用之前检查其有效性,以避免访问已被回收的对象。