泄漏的java库慢慢占用所有内存,没有抛出OutOfMemoryException,变通方法?

时间:2013-05-29 07:07:32

标签: java memory-leaks jvm batch-processing odf

我正在使用java odftoolkit库(simple-odf-0.6.6)进行odf文档操作。我们迭代循环中的所有文档:

TextDocument textdoc = TextDocument.loadDocument(odtFileName);
.
changing content of document
.
textdoc.save(anotherOdtFileName);
textdoc.close();
//then all resources/streams are correctly closed, checked that many times by my colleagues :)

当我们迭代数千个文档时,java app慢慢占用所有内存,然后一切都变慢,因为GC试图释放一些内存。我们没有得到OutOfMemoryException。

我尝试调整JVM内存大小和GC选项(http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html) - 应用程序支持几分钟但​​后来又耗尽了所有内存。

当应用程序到达所有可用内存时,这是从转储中获取的示例:

652.147: [Full GC 652.147: [Tenured: 454655K->454655K(454656K), 2.2387530 secs] 659263K->659216K(659264K), [Perm : 41836K->41836K(42112K)], 2.2388570 secs] [Times: user=2.25 sys=0.00, real=2.23 secs] 
654.387: [Full GC 654.387: [Tenured: 454656K->454656K(454656K), 2.2661510 secs] 659263K->659223K(659264K), [Perm : 41836K->41836K(42112K)], 2.2663190 secs] [Times: user=2.26 sys=0.00, real=2.26 secs] 
656.654: [Full GC 656.654: [Tenured: 454656K->454656K(454656K), 2.4117680 secs] 659263K->659229K(659264K), [Perm : 41836K->41836K(42112K)], 2.4118970 secs] [Times: user=2.41 sys=0.00, real=2.41 secs]

正如您所看到的,只有少数几个已经发布且GC非常慢(超过2秒)。

jmap histogram 显示了最大的消费者:

 num     #instances         #bytes  class name
----------------------------------------------
   1:       2535190       99077856  [C
   2:       2529791       60714984  java.lang.String
   3:         21085       27956544  [B
   4:        389820       16181680  [Ljava.lang.Object;
   5:        147111       13373896  [Ljava.util.HashMap$Entry;
   6:        108426       13180496  <constMethodKlass>
   7:        518834       12452016  java.util.HashMap$Entry
   8:        108426        9547136  <methodKlass>
   9:        321713        7721112  java.util.Vector
  10:        306308        7351392  org.apache.xerces.dom.AttributeMap
  11:        144353        6928944  java.util.HashMap
  12:         10230        5879960  <constantPoolKlass>
  13:         10230        5089344  <instanceKlassKlass>
  14:        114065        4562600  org.odftoolkit.odfdom.dom.attribute.text.TextStyleNameAttribute
  15:         58248        4193856  org.odftoolkit.odfdom.incubator.doc.text.OdfTextParagraph
  16:         90041        3601640  org.odftoolkit.odfdom.pkg.OdfAlienAttribute
  17:         36437        3459000  [I
  18:         48609        3110976  java.util.zip.ZipEntry
  19:          7454        2939616  <constantPoolCacheKlass>
  20:         36491        2627352  org.odftoolkit.odfdom.incubator.doc.style.OdfStyle
  21:         36399        2620728  org.odftoolkit.odfdom.incubator.doc.text.OdfTextSpan
  22:         58397        2335880  org.odftoolkit.odfdom.dom.attribute.style.StyleNameAttribute
  23:         65517        2096544  org.apache.xerces.dom.TextImpl
  24:         24270        1747440  org.odftoolkit.odfdom.incubator.doc.text.OdfTextListLevelStyleBullet
  25:         36511        1460440  org.odftoolkit.odfdom.dom.attribute.style.StyleFamilyAttribute
  26:         24335        1362760  org.odftoolkit.odfdom.dom.element.style.StyleParagraphPropertiesElement
  27:         24320        1361920  org.odftoolkit.odfdom.dom.element.style.StyleListLevelPropertiesElement
  28:         24320        1361920  org.odftoolkit.odfdom.dom.element.style.StyleListLevelLabelAlignmentElement
  29:         10933        1316952  java.lang.Class
  30:         29175        1167000  org.odftoolkit.odfdom.dom.attribute.style.StyleParentStyleNameAttribute
  31:         19464        1089984  org.odftoolkit.odfdom.dom.element.style.StyleFontFaceElement
  32:         68003        1088048  java.lang.Integer
  33:          3531        1082640  <methodDataKlass>
  34:         26757        1070280  org.odftoolkit.odfdom.dom.attribute.fo.FoMarginLeftAttribute
  35:         26752        1070080  org.odftoolkit.odfdom.dom.attribute.fo.FoTextIndentAttribute
  36:         24330         973200  org.odftoolkit.odfdom.dom.attribute.style.StyleWritingModeAttribute
  32:         68003        1088048  java.lang.Integer
  33:          3531        1082640  <methodDataKlass>
  34:         26757        1070280  org.odftoolkit.odfdom.dom.attribute.fo.FoMarginLeftAttribute
  35:         26752        1070080  org.odftoolkit.odfdom.dom.attribute.fo.FoTextIndentAttribute
  36:         24330         973200  org.odftoolkit.odfdom.dom.attribute.style.StyleWritingModeAttribute
  37:         24320         972800  org.odftoolkit.odfdom.dom.attribute.text.TextListLevelPositionAndSpaceModeAttribute
  38:         24320         972800  org.odftoolkit.odfdom.dom.attribute.text.TextLevelAttribute
  39:         24320         972800  org.odftoolkit.odfdom.dom.attribute.text.TextListTabStopPositionAttribute
  40:         24320         972800  org.odftoolkit.odfdom.dom.attribute.text.TextLabelFollowedByAttribute
  41:         24315         972600  org.odftoolkit.odfdom.dom.attribute.fo.FoLineHeightAttribute
  42:         24270         970800  org.odftoolkit.odfdom.dom.attribute.text.TextBulletCharAttribute
  43:         17064         955584  org.odftoolkit.odfdom.dom.element.style.StyleTextPropertiesElement
  44:         38372         920928  java.util.ArrayList
  45:         12135         873720  org.odftoolkit.odfdom.dom.element.text.TextAElement

你可以看到内存中有很多与odftoolkit相关的类。

有没有有效的方法来解决这个问题?很高兴有可能在运行时从我们的应用程序卸载odftoolkit并再次加载它以摆脱内存中的所有对象(显然它链接在一起,GC无法做任何有用的事情)。

我们还考虑将关键代码作为较小的文档组的单独进程运行,但这并不能解决问题的原因。

3 个答案:

答案 0 :(得分:4)

您很可能在库中存在泄漏,或者因为您没有正确使用它。

您所看到的糟糕表现是经典的“GC死亡螺旋”行为,其中应用程序花费越来越多的时间运行GC并回收越来越少的内存。在GC发生数分钟或数小时之后,你很可能最终获得OOME。

处理死亡螺旋的方法是限制使用UseGCOverheadLimit JVM切换器在垃圾收集中花费的时间。如果GC占用指定时间的更多时间,则JVM会主动向OOME发送消息“超出GC开销限制”。这是一件好事...一般来说。

然后您尝试追踪存储泄漏。


许多资源都在追踪Java存储泄漏。对于初学者,这里有关于主题的StackOverflow Q&amp; A:

基本思想是使用一个工具(并且有许多工具)来识别泄漏的对象,并通过对象引用链返回来找出为什么可以访问它们什么时候不应该。

答案 1 :(得分:2)

您可以编写一些测试来验证一些事项:

  1. lib本身是否在最简单的情况下泄漏?使用例如代码如:

    public void loadSaveDocument(String fileInName, String fileOutName) throws Exception {
        OutputStream fileOutStream = new FileOutputStream(fileOutName);
        TextDocument textdoc = TextDocument.loadDocument(fileInName);
        textdoc.addParagraph("added text");
        textdoc.save(fileOutStream);
        textdoc.close();
    }
    
  2. 如果是的话,一个务实的解决方案是找到使用前面建议的单独流程的解决方案。

  3. 如果没有,是否有特定文件导致泄漏?尝试通过上面的代码运行所有文档。
  4. 或者,如果没有导致泄漏的特定文件,您正在使用的文件的特定修改是否会导致泄漏?
  5. 如果不是,请检查与应用程序代码相比与简单代码的不同之处。
  6. 我还建议使用像JProfiler这样的分析器并拍摄一些快照。有关如何使用它的答案,请参阅How to find memory leak in java using JProfiler?

答案 2 :(得分:0)

您可以查看odf库,找出您正在使用的版本的任何预先存在的问题。

另外,一种解决方法是通过System.exec调用将所有文档处理分成另一个vm