问题描述
我正在研究Java library,它是Windows Waveform Functions的薄包装,可以通过Java播放24位音频。 (JVM仅支持8位和16位音频。)
Windows波形函数中的范例是:
- 创建标题结构
- 在标题上调用waveOutPrepareHeader。
- 将标题发送到声卡
- 声卡异步播放(这意味着Header必须在音频播放期间保留在内存中)
- 当声卡完成播放后,它会在标题中设置一个“完成”位
- 设置“完成”位后,我必须呼叫
waveOutUnprepareHeader
- 然后我可以从内存中删除标头
鉴于我的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管理。