如何处理外部库的内存泄漏

时间:2014-09-26 08:51:37

标签: java memory-leaks

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

来自库的编码器已知内存问题,但尚未修复,可能永远不会(它与java garbage collector无法正确使用本机界面有关)。由于此特定库没有其他选择,我正在寻找通过顺序执行应用程序来解决任务的选项。

目前,我有一个bash包装器脚本,它获取了应该执行的任务列表,并且脚本调用应用程序的每个任务都只执行此单个任务。

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

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

修改提供更多信息:

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

  • 新进程/ JVM:在此上下文中是否相同?我对java进程api或启动新JVM没有多少经验。我的假设是,这将涉及使用main使用自己的ProcessBuilder.start()函数启动单独的java程序?

  • 数据交换:只有几个kilobytes因此性能不是问题。仍然,没有文件的解决方案将是更可取的,但如果我理解正确memory mapped files也使用本地文件。另一方面,插座听起来很有希望。

2 个答案:

答案 0 :(得分:5)

有趣的是,我遇到了同样的问题。根据定义,您需要接受任何最佳实践,或者不得不使用必须使用但无法升级的错误库。

我们提出的解决方案是在它自己的进程中隔离对库的调用。这个过程是主过程的孩子。主进程包含良好的代码,而子进程包含错误。然后,我们能够跟踪子进程的调用次数,并在达到一定数量后将其拆除。我们知道在子进程损坏之前我们可以使用X调用。

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

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

同样,以上都不是“好”但它对我们有用。

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

答案 1 :(得分:2)

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

如果泄漏确实存在于库暴露的Java代码中,那么就有了一种方法。 Java中的内存泄漏是由于忘记引用,例如请考虑以下示例:

class Foo {

  private ExpensiveObject eo; 

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

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

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

然而,这应该被认为是最后的手段,因为它非常黑客。

或者,更好的方法通常是分叉另一个JVM实例来执行脏工作。看起来你已经在做类似的事了。通过分配JVM,可以隔离进程级别的内存使用。一旦进程终止,操作系统将释放所有内存。这种方法的问题通常是平台兼容性,但是当您已经使用本机库时,这不会使您的情况恶化。

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