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

huangapple go评论92阅读模式
英文:

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

问题

以下是翻译好的内容:

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

Windows波形函数中的范例是:

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

考虑到我的Java库将是本机波形函数的薄包装,我有一个用于头指针的类,因此我可以在需要的时间内保持它的范围,根据需要传递它,并最终在其上调用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设置为可关闭,如果我不调用close(),我不会得到我习惯于的编译器警告。

在这里有没有一种良好的方法来保护自己免受意外的内存泄漏?

英文:

I am working on a Java library that is a thin wrapper for the Windows Waveform Functions to play 24 bit audio through Java. (The JVM only supports 8bit and 16bit audio).

The paradigm in the Windows Waveform Functions is:

  1. Create Header struct
  2. call waveOutPrepareHeader on the Header.
  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
  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.

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:

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 &lt;&lt; &quot;[C++] Header out location: &quot; &lt;&lt; headerOut &lt;&lt; std::endl;
	return (jlong)headerOut;
}

As you can see, I allocate a WAVEHDR on the heap in C++.

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.

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.

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().

Is there a good way to protect myself from accidental memory leaks here?

答案1

得分: 1

一个解决方案是在启动时创建一个WAVEHDR对象池,只允许Java代码从池中获取对象并进行回收。如果未能返回对象,将导致在启动后立即出现空的池,并导致崩溃。

英文:

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. Failure to return objects will result in an empty pool right after startup and a crash.

答案2

得分: 1

你是对的,编译器不会警告您缺少close(),但是lint或类似的静态代码分析工具会。无论如何,Closeable是推荐的方式,如果您在try()中使用它,语言将站在您这一边。不过,从finalize()调用close()也是一个很好的做法(除非您知道您的JVM具有Steven M. Cherry描述的错误)。

> 顺便说一下,他并没有说finalize()导致了内存泄漏;这是一种更严重的堆栈破坏,但这份报告是2008年的,因此您很少有机会在生产中遇到这个错误。

至于WAVEHDR的特定情况,我建议不要在C++中在堆上分配它,而是将其全部(包括缓冲区)作为直接ByteBuffer在Java中分配:

public class WaveHeader {
  private ByteBuffer waveHeader;
  private final static int PTR_LENGTH = 8; // 64位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管理。

英文:

You are right, the compiler won't warn you about missing close(), but lint or similar static code analysis tool, will. At any rate, Closeable is the recommended way to go, and if you use it with try(), the language will be on your side. 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).

> By the way, he did not say that finalize() caused a memory leak; 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.

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:

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&lt;WAVEHDR*&gt;(env-&gt;GetDirectBufferAddress(byteBuffer));
    jlong capacity = env-&gt;GetDirectBufferCapacity(byteBuffer);
    waveHeader-&gt;lpData = reinterpret_cast&lt;LPSTR&gt;(waveHeader+1);
    waveHeader-&gt;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.

huangapple
  • 本文由 发表于 2020年9月8日 10:16:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/63786288.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定