NDK开发之内存管理

NDK开发-内存管理

目录

局部引用

大多数的jni函数,调用以后返回的结果都是局部引用。局部引用在所在函数执行完毕后就会销毁对应的内存空间,和常说的局部变量有区别,赋值给全局变量也没用,局部引用无法跨函数使用。

删除局部引用

一个函数内的局部引用数量是有限制的,在早期的安卓系统中,体现的更为明显。当函数体内需要大量使用局部引用时,比如大循环,最好及时删除不用的局部引用,可以使用env->DeleteLocalRef来删除局部引用。用法如下:

1
2
3
4
5
6
jstring javaString = env->NewStringUTF("Hello from JNI");
const char *str = env->GetStringUTFChars(javaString, NULL);
LOGE("局部引用javastring删除前 is %s",str)
env->DeleteLocalRef(javaString);
const char *str1 = env->GetStringUTFChars(javaString, NULL);
LOGE("局部引用javastring删除后 is %s",str1)

  1. 创建局部引用:调用 env->NewStringUTF("Hello from JNI") 创建一个新的Java字符串对象,并返回一个本地引用 javaString
  2. 打印引用前的内容和地址:使用 env->GetStringUTFChars 获取字符串内容,并打印出来。
  3. 删除本地引用:调用 env->DeleteLocalRef(javaString) 删除本地引用。
  4. 尝试访问已删除的引用:尝试再次访问 javaString,这通常会导致未定义行为(例如崩溃或异常),因为引用已被删除。

通过这个示例,你可以看到删除局部引用前后引用地址和内容的变化,以及尝试访问已删除引用时可能出现的问题。这样可以更好地理解局部引用的概念及其在JNI中的作用。

判断可用局部引用

env->EnsureLocalCapacity(num),用于判断是否有足够的局部引用可以使用,足够则返回0。

1
2
LOGI("可用局部引用数量1000000结果 :%d",env->EnsureLocalCapacity(1000000));
LOGI("可用局部引用数量10000000结果 :%d",env->EnsureLocalCapacity(10000000));

批量管理局部引用

需大量使用局部引用时,手动删除太过麻烦,可以使用以下两个函数来批量管理局部引用。

  • env->PushLocalFrame(num): 创建一个新的局部引用帧,指定可以创建的局部引用的最大数量 num。这个帧将包含在调用 PopLocalFrame 之前创建的所有局部引用。
  • env->PopLocalFrame(result): 销毁当前的局部引用帧,释放该帧中所有局部引用。如果 result 参数不为 nullptr,则将其作为返回值返回给调用者,并将其添加到前一个局部引用帧中。
    • 使用 env->PopLocalFrame(nullptr) 弹出当前的局部引用帧,并释放所有在这个帧中创建的局部引用。
    • 如果需要保留某个局部引用,可以将其作为参数传递给 PopLocalFrame,例如 env->PopLocalFrame(保留的引用)

释放所有局部引用

1
2
3
4
5
6
7
8
jstring javaString;
env->PushLocalFrame(2);
for(int i = 0;i < 2;i++){
javaString = env->NewStringUTF("Hello from JNI");
}
LOGI("str is %s",env->GetStringUTFChars(javaString, NULL))
jstring result = static_cast<jstring>(env->PopLocalFrame(nullptr));
LOGI("str1 is %p",result)

保留局部引用释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
jstring javaString;

env->PushLocalFrame(2);

for(int i = 0;i < 2;i++){

javaString = env->NewStringUTF("Hello from JNI");

}


LOGI("str is %s",env->GetStringUTFChars(javaString, NULL))


jstring result = static_cast<jstring>(env->PopLocalFrame(javaString));


LOGI("str1 is %p",result)

LOGI("str1 is %s",env->GetStringUTFChars(result, NULL))

全局引用

在jni开发中,需要跨函数使用变量时,直接定义全局变量时没用的,需要使用以下两个方法,来创建和删除全局引用

  • env->NewGlobalRef()
  • env->DeleteGlobalRef()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <jni.h>
#include <string>
#include <android/log.h>

#define TAG "hackfun"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)

jclass clazz = nullptr;

extern "C" JNIEXPORT jstring JNICALL
Java_com_wnxwzr_sotest1_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
std::string hello = "Hello from C++";

if (clazz == nullptr) {
LOGE("Global reference is null");
} else {
LOGI("Global reference is valid - %p", clazz);
}

// 你可以在这里使用全局引用 clazz 进行其他操作

return env->NewStringUTF(hello.c_str());
}

extern "C" JNIEXPORT void JNICALL
Java_com_wnxwzr_sotest1_MainActivity_accessFields(JNIEnv *env, jobject, jobject ndkdemoObj) {

// 查找类并创建全局引用
jclass tmpclazz = env->FindClass("com/wnxwzr/sotest1/NdkDemo");
if (tmpclazz == nullptr) {
LOGE("FindClass failed");
} else {
LOGI("FindClass succeeded");
}

clazz = static_cast<jclass>(env->NewGlobalRef(tmpclazz));
if (clazz == nullptr) {
LOGE("NewGlobalRef failed");
} else {
LOGI("NewGlobalRef succeeded");
}

// 删除局部引用
env->DeleteLocalRef(tmpclazz);
}

弱全局引用

与全局引用基本相同,区别是弱全局引用有可能会被回收

  • env->NewWeakGlobalRef();
  • env->DeleteWeakGlobalRef();