我编写了一个简单的Java程序,它从数据库读取一百万行并将它们写入文件。
该程序可以使用的最大内存为512M。
我经常注意到这个程序运行Out Of Memory超过500K行。
由于程序是一个非常简单的程序,因此很容易发现它没有内存泄漏。程序的工作方式是从数据库中获取一千行,使用Streams将它们写入文件,然后获取下一千行。每行的大小各不相同,但没有一行是巨大的。在程序运行时进行转储时,可以在堆上轻松看到旧字符串。堆中的这些String无法访问,这意味着他们正在等待收集垃圾。我也相信GC在执行这个程序时不一定会运行,这会使String在堆中的时间超出它们应该的时间。
我认为解决方案是使用长Char Arrays(或Stringbuffer)而不是使用String对象来存储DB返回的行。假设我可以覆盖Char数组的内容,这意味着可以在多次迭代中使用相同的Char数组,而不必每次都分配新的空间。
伪代码:
如果上面的伪代码修复了我的问题,那么实际上String类的不可变特性会伤害Java程序员,因为即使字符串不再使用,也没有直接的方法来声明字符串占用的空间。 / p>
这个问题有更好的替代方案吗?
P.S:我没有单独进行静态分析。我使用yourkit profiler来测试堆转储。转储显然说96%的字符串没有GC根,这意味着他们正在等待收集垃圾。我也不在代码中使用Substring。答案 0 :(得分:2)
班级String
的不可变性与OutOfMemoryError
完全无关。不变性意味着它永远不会改变,只有那样。
如果内存不足,只是因为垃圾收集器无法找到任何垃圾到收集。
在实践中,很可能你在内存中持有太多字符串的引用(例如,你是否有任何类型的字符串集合,例如List,Set,Map?)。您必须销毁这些引用以允许垃圾收集器完成其工作并释放一些内存。
答案 1 :(得分:1)
这个问题的简单答案是'不'。我怀疑你的参考时间比你想象的要长。
您是否正确关闭了这些流?你是intern()
那些字符串吗?如果字符串不存在则会导致永久复制由字符串组成,并占用 permgen 空间(未收集)。您正在使用更大字符串的substring()
吗?字符串使用flyweight模式,如果使用substring()
创建,则将共享字符数组。有关详细信息,请参阅here。
您建议垃圾收集未运行。选项-verbose:gc
将记录垃圾收集,您可以立即看到正在发生的事情。
答案 2 :(得分:1)
唯一可能导致OutOfMemoryError的字符串就是保留一个更大的字符串的小部分。如果你这样做,那么从堆转储就应该是显而易见的。
当您进行堆转储时,我建议您只查看活动对象,在这种情况下,您不需要的任何保留对象最有可能是代码中的错误。