简体   繁体   English

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

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

I am working on a Java library that is a thin wrapper for the Windows Waveform Functions to play 24 bit audio through Java.我正在开发一个Java 库,它是Windows 波形函数的一个瘦包装器,可以通过 Java 播放 24 位音频。 (The JVM only supports 8bit and 16bit audio). (JVM 仅支持 8 位和 16 位音频)。

The paradigm in the Windows Waveform Functions is: Windows 波形函数中的范例是:

  1. Create Header struct创建标题结构
  2. call waveOutPrepareHeader on the Header.在 Header 上调用 waveOutPrepareHeader。
  3. Send header to sound card将标头发送到声卡
  4. Sound card plays asynchronously (which means the Header must stay in memory for the duration of the audio playing)声卡异步播放(这意味着标题必须在音频播放期间留在内存中)
  5. When the sound card is done playing, it sets a "Done" bit in the header当声卡播放完毕后,它会在标题中设置一个“完成”位
  6. When the "done" bit is set, I have to call waveOutUnprepareHeader设置“完成”位后,我必须调用waveOutUnprepareHeader
  7. Then I can remove the header from memory然后我可以从内存中删除标题

Given that my Java library is going to be a thin wrapper for the native Waveform Functions, I have a class for the Header Pointer, so I can keep it in scope for as long as needed, pass it around as needed, and eventually call waveOutUnprepareHeader on it.鉴于我的 Java 库将成为本机波形函数的瘦包装器,我有一个用于 Header Pointer 的类,因此我可以根据需要将其保留在范围内,根据需要传递它,并最终调用waveOutUnprepareHeader在上面。

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

The native code being called above on line 5 ( HWaveOut.createHeader() ) is:上面第 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;
}

As you can see, I allocate a WAVEHDR on the heap in C++.如您所见,我在 C++ 中在堆上分配了一个WAVEHDR

It is my understanding that I am responsible for de-allocating the WAVEHDR when I am done with it -- that the Java Garbage Collector won't destroy it for me.我的理解是,当我完成 WAVEHDR 时,我有责任解除它的分配——Java 垃圾收集器不会为我销毁它。

I initially considered putting the de-allocation code in finalize() in java, so that the C++ struct is always automatically de-allocated when the java object is garbage-collected in java, but according to this answer this method will cause memory leaks.我最初考虑将取消分配代码放在 java 中的finalize()中,这样当 java 对象在 java 中被垃圾收集时,C++ 结构总是自动取消分配,但根据这个答案,这种方法会导致内存泄漏。

I then thought of using the compiler warnings for unclosed resources in the classes like InputStream to catch any mistakes I make, but even if I make WaveHeader Closable , I don't get the compiler warnings I'm used to if I don't call close() .然后我想用没有结束的资源编译器警告中的类象的InputStream的赶上我犯任何错误,但即使我做WaveHeader Closable ,我不明白我已经习惯了,如果我不叫编译器警告close()

Is there a good way to protect myself from accidental memory leaks here?这里有没有什么好的方法可以保护自己免受意外内存泄漏的影响?

One solution is to create a pool of these WAVEHDR objects at startup and only allow the Java code to take objects from the pool and recycle them.一种解决方案是在启动时创建这些WAVEHDR对象的池,并且只允许 Java 代码从池中取出对象并回收它们。 Failure to return objects will result in an empty pool right after startup and a crash.未能返回对象将在启动和崩溃后立即导致一个空池。

You are right, the compiler won't warn you about missing close() , but lint or similar static code analysis tool, will.您是对的,编译器不会警告您缺少close() ,但是lint或类似的静态代码分析工具会。 At any rate, Closeable is the recommended way to go, and if you use it with try() , the language will be on your side.无论如何, Closeable是推荐的方法,如果你将它与try() ,语言将站在你这边。 Still, it's a good practice to call close() from finalize() (unless you know that your JVM has the bug described by Steven M. Cherry ).尽管如此,从finalize()调用close()是一个很好的做法(除非您知道您的 JVM 有Steven M. Cherry 描述错误)。

By the way, he did not say that finalize() caused a memory leak;顺便说一句,他并没有说finalize()导致了内存泄漏; this was a heap corruption , something much worse;这是一个堆损坏,更糟糕的事情; but this report is of 2008, so you have little chance to encounter this bug in production.但这份报告是 2008 年的,所以你几乎没有机会在生产中遇到这个错误。

As for the specific case of WAVEHDR , I would suggest not to allocate it in C++ on heap, but rather to keep it all (with the buffer) allocated in Java as a direct ByteBuffer:至于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);
}

Now you don't care about close() or managing the memory of your C++ object: it is all managed by Java.现在您不再关心close()或管理 C++ 对象的内存:它全部由 Java 管理。

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

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