我正在开发一个在Windows Mobile设备上运行的Java应用程序。为了实现这一目标,我们一直在使用Esmertec JBed JVM,这并不完美,但我们现在仍然坚持使用它。最近我们收到客户关于OutOfMemoryErrors的投诉。在玩了很多东西之后,我发现设备有足够的可用内存(大约4MB)。
OutOfMemoryErrors总是出现在代码中的同一点,也就是扩展StringBuffer以便向其附加一些字符时。在这个区域添加了一些日志记录后,我发现我的StringBuffer中有大约290000个字符,容量大约是290500.内部字符数组的扩展策略只是将大小加倍,所以它会尝试分配一个数组大约580000个字符。我打印出这段时间的内存使用量,发现它总共使用了大约3.8MB的大约6.8MB(尽管我已经看到总可用内存有时会增加到12MB左右,因此有足够的扩展空间)。所以在这一点上,应用程序报告了一个OutOfMemoryError,考虑到有多少仍然是免费的,这没有多大意义。
到目前为止,我开始考虑应用程序的运行。基本上发生的事情是我正在使用MinML(一个小的XML Sax Parser)解析XML文件。 XML中的一个字段中包含大约300k个字符。解析器从磁盘流式传输数据,默认情况下,它一次只加载256个字符。因此,当它到达相关字段时,解析器将调用处理程序的'characters()'方法超过1000次。每次创建一个包含256个字符的新char []。处理程序只是将这些字符附加到StringBuffer。 StringBuffer的默认初始大小只有12,因此当字符附加到缓冲区时,它必须增长很多次(每次创建一个新的char [])。
我的假设是,有可能虽然有足够的可用内存,因为前面的char []可以被垃圾收集,但是可能没有足够大的连续内存块来适应我想要分配的新数组。也许JVM不够智能,无法扩展堆大小,因为它很愚蠢,认为没有必要,因为显然有足够的可用内存。
所以我的问题是:是否有人对此JVM有任何经验,并且可能最终确认或反驳我对内存分配的假设?而且,是否有人有任何想法(假设我的假设是正确的)关于如何改进数组的分配以使内存不会变得支离破碎?
注意:我已经尝试过的事情:
做这两件事有点帮助,但随着我增加xml数据的大小,我仍然会以相当低的尺寸(约350kb)获得OutOfMemoryErrors。
要添加的另一件事:所有这些测试都是在使用相关JVM的设备上执行的。如果我使用Java SE 1.2 JVM在桌面上运行相同的代码,我没有任何问题,或者至少我没有遇到问题,直到我的数据大小达到4MB。
编辑:
我刚试过的另一件事有点帮助我将Xms设置为10M。所以这就解决了JVM在不应该扩展堆时的问题,并允许我在错误发生之前处理更多数据。
答案 0 :(得分:2)
也许你可以试试VTD光。它看起来比SAX更有效。 (我知道这是一个巨大的变化。)
答案 1 :(得分:2)
为了更新我自己的问题,我发现最好的解决方案是设置最小堆大小(我将其设置为10M)。这意味着JVM永远不必决定是否扩展堆,因此即使它应该有足够的空间,它也永远不会(到目前为止在测试中)死于OutOfMemoryError。到目前为止,在测试中我们已经能够将我们解析的数据量增加三倍而没有错误,如果我们真的需要,我们可能会更进一步。
对于快速解决方案来保持现有客户满意度这是一个黑客攻击,但我们现在正在寻找一个不同的JVM,如果JVM更好地处理这个scneario,我会报告更新。
答案 2 :(得分:1)
根据我对JVM的了解,碎片绝不应该是 要解决的问题。如果没有更多的分配空间 - 无论是否由于碎片 - 垃圾收集器应该运行,而GC通常也会压缩数据以解决碎片问题。
要强调 - 在GC运行之后,您只会出现“内存不足”错误,并且仍然无法释放足够的内存。
我会尝试深入研究您正在运行的特定JVM的选项。例如,“复制”垃圾收集器一次只使用一半可用内存,因此更改VM以使用其他内容可能会释放一半的内存。
我并不是真的建议您的VM使用简单的复制GC,我只是建议在VM级别上进行探测。
答案 3 :(得分:0)
我认为你有足够的内存,但是正在创建大量的引用对象。试试这篇文章:https://web.archive.org/web/1/http://articles.techrepublic%2ecom%2ecom/5100-10878_11-1049545.html?tag=rbxccnbtr1了解更多信息。
答案 4 :(得分:0)
我不确定是否在MinML中分配了这些StringBuffers - 如果是这样,我假设你有源代码?如果你这样做,那么也许当你扫描字符串时,如果字符串达到一定长度(比如10000字节),你可以向前看以确定字符串的确切长度,并重新分配一个缓冲区到那个大小。这很难看,但它可以节省内存。 (它甚至可能比不做前瞻更快,因为你可能会保存 许多重新分配。)
如果不可以访问MinML源,那么我不确定StringBuffer的生命周期是什么相对于XML文档。但是这个建议(虽然它比上一个更加丑陋)可能仍然有用:既然你是从磁盘上获取XML,也许你可以使用(比方说)一个SAX解析器来预解析它,只是为了获得字符串的大小字段,并相应地分配StingBuffers?
答案 5 :(得分:0)
您是否能够从设备获取堆转储?
如果获得堆转储并且它是兼容格式,则某些Java内存分析器会提供有关连续内存块大小的信息。我记得在IBM Heap Analyzer http://www.alphaworks.ibm.com/tech/heapanalyzer中看到了这个功能,但也检查了更新的Eclipse Memory Analyzer http://www.eclipse.org/mat/
如果你有可能修改XML文件,那可能是最快的出路。 Java中的XML解析始终是内存密集型的,300K对于单个字段来说是相当多的。相反,您可以尝试将此字段分隔为单独的非xml文件。