我正在开发一个Java应用程序,该应用程序在Windows Mobile设备上运行。为了实现这一点,我们一直在使用Esmertec JBed JVM,尽管它不完美,但我们暂时无法更改。最近,我们收到了客户有关OutOfMemoryErrors的投诉。经过多次试验,我发现设备有足够的可用内存(约4MB)。
OutOfMemoryErrors总是发生在代码的同一点上,即在扩展StringBuffer以追加一些字符时。在此区域周围添加一些日志后,我发现我的StringBuffer大约有290000个字符,容量约为290500。内部字符数组的扩展策略仅是将其大小加倍,因此它将尝试分配大约580000个字符的数组。同时,我还打印出了此时的内存使用情况,并发现它使用的约为6.8MB中的3.8MB(尽管我有时看到可用总内存上升到约12MB,因此还有很大的扩展空间)。因此,在这一点上,应用程序报告了OutOfMemoryError,考虑到还有这么多可用内存,这显然不合理。
我开始思考应用程序在此之前的操作。基本上是我正在使用MinML(一个小型XML Sax解析器)解析XML文件。 XML中的一个字段有大约300k个字符。解析器从磁盘流式传输数据,并默认每次仅加载256个字符。因此,当它到达涉及该字段的位置时,解析器将调用处理程序的“characters()”方法超过1000次。每次它将创建一个新的char []来容纳256个字符。处理程序只需将这些字符附加到StringBuffer中。 StringBuffer的默认初始大小仅为12,因此随着字符附加到缓冲区中,它将不得不多次增长(每次都会创建一个新的char [])。
我的假设是,虽然之前的char[]数组可以被垃圾收集,因此有足够的空闲内存,但也有可能没有足够大的连续内存块来适应我尝试分配的新数组。也许JVM并不聪明,不能扩展堆大小,因为它认为没有必要,显然有足够的空闲内存。
所以我的问题是:是否有人有关于这个JVM的经验,并且能够确定地证实或否定我的内存分配假设?另外,如果我的假设是正确的,是否有任何想法来改进数组的分配,使得内存不会变得碎片化?
注意:我已经尝试过的事情:
- 增加StringBuffer的初始数组大小和增加解析器的读取大小,以便不需要创建太多的数组。
- 更改StringBuffer的扩展策略,使其达到一定大小阈值后,只扩展25%而不是100%。
这两个方法都有所帮助,但当我增加输入的xml数据大小时,仍然会在相当低的大小(约350kb)处遇到OutOfMemoryErrors。
另外要补充的一点是:所有这些测试都是在使用该JVM的设备上进行的。如果我在桌面上使用Java SE 1.2 JVM运行相同的代码,则没有任何问题,或者至少在数据达到约4MB大小之前不会出现问题。
编辑:
我刚刚尝试了另一件事情,这有点帮助,那就是我将Xms设置为10M。因此,这解决了JVM应该扩展堆时未扩展堆的问题,并允许我在出现错误之前处理更多的数据。