繁体   English   中英

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

[英]How to manage memory deallocation properly between C++ and Java with JNI?

我正在开发一个Java 库,它是Windows 波形函数的一个瘦包装器,可以通过 Java 播放 24 位音频。 (JVM 仅支持 8 位和 16 位音频)。

Windows 波形函数中的范例是:

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

鉴于我的 Java 库将成为本机波形函数的瘦包装器,我有一个用于 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, 0, 0, 0, 0, 0 };
    std::cout << "[C++] Header out location: " << headerOut << std::endl;
    return (jlong)headerOut;
}

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

我的理解是,当我完成 WAVEHDR 时,我有责任解除它的分配——Java 垃圾收集器不会为我销毁它。

我最初考虑将取消分配代码放在 java 中的finalize()中,这样当 java 对象在 java 中被垃圾收集时,C++ 结构总是自动取消分配,但根据这个答案,这种方法会导致内存泄漏。

然后我想用没有结束的资源编译器警告中的类象的InputStream的赶上我犯任何错误,但即使我做WaveHeader Closable ,我不明白我已经习惯了,如果我不叫编译器警告close()

这里有没有什么好的方法可以保护自己免受意外内存泄漏的影响?

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

您是对的,编译器不会警告您缺少close() ,但是lint或类似的静态代码分析工具会。 无论如何, Closeable是推荐的方法,如果你将它与try() ,语言将站在你这边。 尽管如此,从finalize()调用close()是一个很好的做法(除非您知道您的 JVM 有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 管理。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM