【问题标题】:How to deal with memory leaks from external library如何处理来自外部库的内存泄漏
【发布时间】:2014-11-21 05:35:21
【问题描述】:

我有一个小型java 应用程序运行一组计算繁重的任务。为了处理任务,我使用了一个外部库,它通过本地方法和一些C 代码完成大部分计算。不幸的是,在解决了一项任务后,该库会遭受严重的内存泄漏,因此每次应用程序执行只能解决一项任务。

库中的编码人员知道内存问题,但尚未修复,也许永远不会修复(它与 java garbage collector 无法正确使用本机接口有关)。由于这个特定的库没有其他选择,我正在寻找通过顺序执行应用程序来解决任务的选项。

目前,我有一个 bash 包装器脚本,它获取应执行的任务列表,并且对于每个任务,脚本调用应用程序并仅执行这个单一任务。

由于任务通常需要之前任务的结果,这涉及到serializingdeserializing 执行结果到文件。这对我来说似乎不是一个好习惯,也是因为用户基本上没有办法与程序控制流进行交互。

有没有人知道如何在一个 Java 应用程序中执行这个顺序任务?我想这将涉及为每个任务执行启动一个新的 JVM,希望只将任务结果而不是内存泄漏从新 JVM 传输到我的应用程序。

编辑提供更多信息:

  • 改变问题的根源:不幸的是,该库不是开源的,我既无法访问本机方法,也无法访问 java 接口 api。

  • 新进程/JVM:在这种情况下是否相同?我对 java 进程 api 或启动新的 JVM 没有太多经验。我的假设是这将涉及使用ProcessBuilder.start() 启动一个具有自己的main 函数的单独java 程序?

  • 数据交换:只有几个kilobytes,所以性能不是问题。尽管如此,没有文件的解决方案会更好,但如果我理解正确memory mapped files 也使用本地文件。另一方面,套接字听起来很有希望。

【问题讨论】:

  • 可惜 JVM 没有相当于 AppDomain(隔离什么?).. 无论如何,提到的方法,启动一个单独的进程/JVM 并使用 IPC 传输数据,听起来像一个“合适的”破解;禁止实际使用实现一半正确的库。 (但也许有一些方法可以手动调用来释放底层资源?如果存在这些应该记录在案。)
  • 不能直接调用JNI下面的支持C库吗?然后你可以更好地控制内存并切断垃圾收集器。

标签: java memory-leaks


【解决方案1】:

有趣的是,我也遇到了同样的问题。根据定义,您需要接受没有什么是最佳实践或面对不得不使用您必须使用但无法升级的错误库的情况。

我们想出的解决方案是在它自己的进程中隔离对库的调用。该进程是主进程的子进程。主进程包含好代码,子进程包含坏代码。然后,我们能够跟踪子进程的调用次数,并在达到一定数量时将其删除。我们知道我们可以在子进程损坏之前摆脱 X 调用。

由于我们问题的性质,提出一个新进程使我们能够在重复之前进行另一个 X 调用。

任何状态都在成功调用时返回到主进程。在不成功的调用期间收集的任何状态都被丢弃,我们重新开始。

再一次,以上都不是“好的”,但它对我们有用。

对于它的价值,如果我再次这样做,我会使用 Akka 和远程 Actor,这将使所有子流程、远程处理等变得更加简单。

【讨论】:

    【解决方案2】:

    这取决于。你有这个外部应用程序的源代码,即你能重新编译它吗?最简单的方法显然是从根本上修复泄漏。然而,这可能是不切实际的。如果如您所说,该库是通过本机方法和一些 C 代码实现的,我认为问题与 与 Java 垃圾收集器无法正常工作有关 >。本机方法和 C 代码通常不会将它们的数据存储在 JVM 的堆上,因此不会被垃圾回收,即库的工作是自行清理。

    如果泄漏确实存在于库公开的 Java 代码中,那么就有办法了。 Java 中的内存泄漏是由 忘记 引用引起的,例如考虑以下示例:

    class Foo {
    
      private ExpensiveObject eo; 
    
      Foo(ExpensiveObject eo) {
        this.eo = eo;
      }
    }
    

    只要引用Foo 实例,ExpensiveObject 就(至少)是活动的。如果您(或您的库)没有足够好地隔离实例生命周期,您就会遇到麻烦。如果你没有机会重构,你可以使用反射来清理代码中另一个地方的最大混乱:

    void release(Foo foo) {
      Field f = Foo.class.getDeclaredField("eo");
      f.setAccessible(true);
      f.set(foo, null);
    }
    

    然而,这应该被认为是最后的手段,因为它是一个相当黑客。

    另外,更好的方法通常是派生另一个 JVM 实例来完成脏活。看来您已经在做类似的事情了。通过 fork JVM,您可以在进程级别上隔离内存的使用。一旦进程终止,所有内存都由操作系统释放。这种方法的问题通常是平台兼容性,但由于您已经使用本机库,这不会使您的情况恶化。

    您说您当前使用文件在这些不同的进程之间进行通信。为什么需要将数据存储在文件中?而是consider using sockets or memory-mapped files (NIO),如果性能对这件事很重要。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-10
      • 2012-10-15
      • 1970-01-01
      • 1970-01-01
      • 2015-06-14
      • 1970-01-01
      • 2015-12-09
      相关资源
      最近更新 更多