【问题标题】:How to manage memory deallocation properly between C++ and Java with JNI?如何使用 JNI 在 C++ 和 Java 之间正确管理内存释放?
【发布时间】:2020-09-08 02:16:23
【问题描述】:

我正在开发一个Java library,它是Windows Waveform Functions 通过Java 播放24 位音频的薄包装。 (JVM 只支持 8bit 和 16bit 音频)。

Windows 波形函数中的范例是:

  1. 创建标头结构
  2. 在 Header 上调用 waveOutPrepareHeader。
  3. 向声卡发送标头
  4. 声卡异步播放(这意味着 Header 在音频播放期间必须保留在内存中)
  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++结构总是会自动解除分配,但是根据this answer这个方法会导致内存泄漏。

然后我想在 InputStream 之类的类中对未关闭的资源使用编译器警告来捕捉我犯的任何错误,但即使我犯了 WaveHeader Closable,我也没有收到编译器警告我'如果我不打电话给close(),我已经习惯了。

有什么好的方法可以保护自己免受意外的内存泄漏吗?

【问题讨论】:

  • 我的理解是,当我完成 WAVEHDR 后,我负责解除分配 -- 无论您是否使用 Java,情况都是如此。如果这纯粹是一个 C++ 应用程序,你将如何处理?
  • 我有一个可以正常工作的 C++ 解决方案。我可以用 Java 编写那个确切的解决方案,我可以立即设计。我在问Java中是否有一些机制可以用来帮助避免程序员的错误,因为Java程序员记住他们必须负责任地取消分配资源是不直观的。
  • 如果在 Java 中没有适当的清理功能,那么您将陷入创建和跟踪 C++ 代码中分配的内存的困境。此外,如果您已经清楚地记录了如何使用这些功能,那是程序员的错,因为没有遵循文档。 Java 程序员必须意识到他们正在调用一种非垃圾收集语言,该语言正在动态创建对象,删除是在该语言的控制之下,而不是 Java。

标签: java c++ java-native-interface


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    你是对的,编译器不会警告你缺少close(),但lint 或类似的静态代码分析工具会。无论如何,Closeable 是推荐的方式,如果您将其与try() 一起使用,该语言将支持您。尽管如此,从finalize() 调用close() 还是一个好习惯(除非你知道你的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 管理。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-06-21
      • 2012-04-06
      • 1970-01-01
      • 1970-01-01
      • 2020-06-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多