安卓逆向之NDK内存管理

一:内存管理

在 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);
}

代码解释

  1. 获取类引用:使用 FindClass 获取父类 Parent 的引用。
  2. 获取方法 ID:使用 GetMethodID 获取 greet 方法的 ID。
  3. 调用父类方法:使用 CallNonvirtualVoidMethod 调用父类的 greet 方法。
  4. 释放局部引用:确保释放所有局部引用以避免内存泄漏。

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
  • 使用 PushLocalFramePopLocalFrame:这两个方法允许你在局部引用帧中管理局部引用的生命周期。
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 中,除了基本的局部引用管理函数 NewLocalRefDeleteLocalRef 外,还有一些其他相关的函数,可以帮助管理和优化局部引用的使用。以下是一些重要的局部引用相关函数及其用途:

1. PushLocalFramePopLocalFrame

  • 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. GetMethodIDGetFieldID

  • GetMethodIDGetFieldID:虽然这些函数本身不创建局部引用,但它们返回的方法和字段 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. GetStringUTFCharsReleaseStringUTFChars

  • 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);
}

全局引用的注意事项

  1. 内存管理:全局引用不会自动释放,因此必须在不再需要时手动删除,以避免内存泄漏。
  2. 线程安全:全局引用可以在多个线程中使用,但需要确保在多线程环境中对全局引用的访问是线程安全的。
  3. 局部引用与全局引用的选择:如果只需在单个 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);

使用弱全局引用的场景

  1. 避免内存泄漏:在某些情况下,使用弱全局引用可以防止强引用导致的内存泄漏。例如,当你想要缓存对象,但又不希望阻止它们被垃圾回收时,可以使用弱全局引用。
  2. 缓存对象:在某些情况下,可以使用弱全局引用缓存对象,以便在需要时访问,但不阻止对象被回收。
  3. 事件处理:在事件处理或回调机制中,使用弱全局引用可以避免循环引用的问题。

检查弱全局引用的有效性

由于弱全局引用在对象被垃圾回收后会变为 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 对象的生命周期。它们允许程序在不阻止对象被垃圾回收的情况下引用对象,从而帮助避免内存泄漏和循环引用。使用弱全局引用时,确保在使用之前检查其有效性,以避免访问已被回收的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值