在 Java 中分配大量数组时避免内存碎片

我正在开发一个在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 中的一个字段包含大约 30 万个字符。解析器从磁盘流式传输数据,默认情况下,它一次仅加载 256 个字符。因此,当它到达有问题的字段时,解析器将调用处理程序的“character()”方法超过1000次。每次它都会创建一个包含 256 个字符的新 char[]。处理程序只是将这些字符追加到 StringBuffer。StringBuffer 的默认初始大小仅为 12,因此当字符追加到缓冲区时,它将不得不增长数倍(每次创建一个新的 char[])。

我的假设是,虽然由于以前的char[]可以被垃圾回收,但可能没有足够的可用内存来容纳我试图分配的新数组。也许JVM不够智能,无法扩展堆大小,因为它是愚蠢的,并且认为没有必要,因为显然有足够的可用内存。

所以我的问题是:是否有人对这个JVM有任何经验,并且能够最终确认或反驳我对内存分配的假设?而且,有没有人对如何分配数组以使内存不会变得碎片化有任何想法(假设我的假设是正确的)?

注意:我已经尝试过的事情:

  • 我增加了 StringBuffer 的初始数组大小,并增加了解析器的读取大小,这样它就不需要创建这么多数组。
  • 我更改了 StringBuffer 的扩展策略,以便一旦达到一定的大小阈值,它只会扩展 25%,而不是 100%。

做这两件事都有一点帮助,但是随着我增加xml数据的大小,我仍然得到了相当低的大小(大约350kb)的OutOfMemoryErrors。

另一件需要补充的事情是:所有这些测试都是在使用相关JVM的设备上执行的。如果我使用Java SE 1.2 JVM在桌面上运行相同的代码,我没有任何问题,或者至少在我的数据达到大约4MB之前我不会遇到问题。

编辑:

我刚刚尝试的另一件事很有帮助,那就是我将Xms设置为10M。因此,这解决了JVM没有在应该扩展堆的时候的问题,并允许我在错误发生之前处理更多数据。


答案 1

也许你可以试试VTD灯。它似乎比SAX更省钱。(我知道这是一个巨大的变化。


答案 2

只是为了更新我自己的问题,我发现最好的解决方案是设置最小堆大小(我将其设置为10M)。这意味着JVM永远不必决定是否扩展堆,因此它永远不会(到目前为止在测试中)因OutOfMemoryError而死亡,即使它应该有足够的空间。到目前为止,在测试中,我们已经能够将解析的数据量增加两倍而不会出现错误,如果我们确实需要,我们可能会走得更远。

对于一个让现有客户满意的快速解决方案来说,这有点像一个黑客,但我们现在正在研究一个不同的JVM,如果JVM更好地处理这个scneario,我会报告更新。