如何使用JNI在C ++和Java之间正确管理内存释放?

问题描述

我正在研究Java library,它是Windows Waveform Functions的薄包装,可以通过Java播放24位音频。 (JVM仅支持8位和16位音频。)

Windows波形函数中的范例是:

  1. 创建标题结构
  2. 在标题上调用waveOutPrepareHeader。
  3. 将标题发送到声卡
  4. 声卡异步播放(这意味着Header必须在音频播放期间保留在内存中)
  5. 当声卡完成播放后,它会在标题中设置一个“完成”位
  6. 设置“完成”位后,我必须呼叫waveOutUnprepareHeader
  7. 然后我可以从内存中删除标头

鉴于我的Java库将成为本机Waveform Functions的精简包装,我有一个Header Pointer的类,因此我可以将其保留在所需的范围内,并根据需要传递,以及最终在其上调用waveOutUnprepareHeader

public class WaveHeader {
  long waveHeaderPointer;
  
  public WaveHeader(byte[] buffer) {
    waveHeaderPointer = HWaveOut.createHeader(buffer,buffer.length);
  }
}

在第5行(HWaveOut.createHeader())上方被调用的本机代码是:

JNIEXPORT jlong JNICALL Java_net_joshuad_waveformjni_HWaveOut_createHeader
(JNIEnv * env,jclass jclass,jbyteArray jbuffer,jint jBufferSize) {
    char* buffer = new char[jBufferSize];
    asCharArray(env,jbuffer,buffer);
    WAVEHDR* headerOut = new WAVEHDR{ buffer,(DWORD)jBufferSize,0 };
    std::cout << "[C++] Header out location: " << headerOut << std::endl;
    return (jlong)headerOut;
}

如您所见,我在C ++中的堆上分配了一个WAVEHDR

据我了解,使用完WAVEHDR后,我有责任解除分配-Java垃圾收集器不会为我破坏它。

我最初考虑将取消分配代码放在Java的finalize()中,以便在Java对象在Java中被垃圾回收时,总是自动取消分配C ++结构,但是根据{{3} }这种方法将导致内存泄漏。

然后,我想到了对this answer之类的类中的未关闭资源使用编译器警告,以捕获我犯的任何错误,但是即使我犯了WaveHeader Closable,我也不会如果不调用close(),则会得到我习惯的编译器警告。

这里是否有保护自己免受意外内存泄漏的好方法?

解决方法

一种解决方案是在启动时创建这些WAVEHDR对象的池,并且仅允许Java代码从池中获取对象并回收它们。无法返回对象将导致启动后立即出现空池并崩溃。

,

是的,编译器不会警告您缺少close(),但是 lint 或类似的静态代码分析工具会。无论如何,建议使用Closeable,如果将它与try()配合使用,该语言将在您身边。不过,从close()调用finalize()是个好习惯(除非您知道JVM具有bug described by Steven M. Cherry)。

顺便说一句,他没有说finalize()引起了内存泄漏。这是堆损坏,更糟的是;但是此报告是2008年的,因此您几乎没有机会在生产中遇到此错误。

对于WAVEHDR的特定情况,我建议不要在C ++堆上分配它,而应将所有(带有缓冲区)保留在Java中作为直接ByteBuffer分配:

public class WaveHeader {
  private ByteBuffer waveHeader;
  private final static int PTR_LENGTH = 8; // 64-bit Windows
  private final static int DWORD_LENGTH = 4;
  private final static int WAVEHDR_LENGTH = 4*PTR_LENGTH + 4*DWORD_LENGTH;
  
  private native static void init(ByteBuffer waveHeader);

  public WaveHeader(int bufferLength) {
    waveHeader = allocateDirect(bufferLength + WAVEHDR_LENGTH);
    init(waveHeader);
  }
}
JNIEXPORT void JNICALL Java_net_joshuad_waveformjni_WaveHeader_init(JNIEnv* env,jclass clazz,jobject byteBuffer) {
    auto waveHeader = reinterpret_cast<WAVEHDR*>(env->GetDirectBufferAddress(byteBuffer));
    jlong capacity = env->GetDirectBufferCapacity(byteBuffer);
    waveHeader->lpData = reinterpret_cast<LPSTR>(waveHeader+1);
    waveHeader->dwBufferLength = capacity-sizeof(WAVEHDR);
}

现在,您无需关心close()或管理C ++对象的内存:它们全部由Java管理。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...