我编写了一个简单的 Java 程序,它从数据库中读取一百万行并将它们写入文件。
该程序可以使用的最大内存为 512M。
我经常注意到这个程序运行了超过 500K 行的内存不足。
由于该程序是一个非常简单的程序,因此很容易发现它没有内存泄漏。该程序的工作方式是它从数据库中获取一千行,使用 Streams 将它们写入一个文件,然后去获取接下来的一千行。每行的大小各不相同,但没有一行很大。在程序运行时进行转储时,在堆上很容易看到较旧的字符串。这些堆中的字符串无法访问,这意味着它们正在等待收集垃圾。我也相信 GC 不一定在这个程序的执行过程中运行,这会使 String 在堆中的时间比它们应该的要长。
我认为解决方案是使用长字符数组(或字符串缓冲区)而不是使用字符串对象来存储数据库返回的行。假设我可以覆盖字符数组的内容,这意味着可以在多次迭代中使用相同的字符数组,而不必每次都分配新的空间。
伪代码:
- 使用 new char[1000][1000] 创建一个数组数组;
- 将数据库中的一千行填充到数组中。
- 将数组写入文件。
- 对接下来的一千行使用相同的数组
如果上面的伪代码解决了我的问题,那么实际上 String 类的不可变特性会伤害 Java 程序员,因为即使 String 不再使用,也没有直接的方法来声明 String 使用的空间。
这个问题有更好的选择吗?
PS:我没有单独进行静态分析。我使用 yourkit profiler 来测试堆转储。转储清楚地表明 96% 的字符串没有 GC 根,这意味着它们正在等待收集垃圾。另外我不在我的代码中使用 Substring。