【问题标题】:Does Immutability of Strings in Java cause Out Of MemoryJava中字符串的不变性是否会导致内存不足
【发布时间】:2012-10-06 07:11:59
【问题描述】:

我编写了一个简单的 Java 程序,它从数据库中读取一百万行并将它们写入文件。

这个程序可以使用的最大内存是512M。

我经常注意到这个程序运行 Out Of Memory 超过 500K 行。

由于该程序是一个非常简单的程序,很容易发现它没有内存泄漏。该程序的工作方式是它从数据库中获取一千行,使用 Streams 将它们写入一个文件,然后去获取接下来的一千行。每行的大小各不相同,但没有一行很大。在程序运行时进行转储时,在堆上很容易看到较旧的字符串。这些堆中的字符串无法访问,这意味着它们正在等待收集垃圾。我也相信 GC 不一定会在这个程序的执行过程中运行,这会使 String 在堆中的时间比它们应该的要长。

我认为解决方案是使用长字符数组(或字符串缓冲区)而不是使用字符串对象来存储数据库返回的行。假设我可以覆盖字符数组的内容,这意味着可以在多次迭代中使用相同的字符数组,而不必每次都分配新的空间。

伪代码:

  1. 使用 new char[1000][1000] 创建一个数组数组;
  2. 将数据库中的一千行填充到数组中。
  3. 将数组写入文件。
  4. 对接下来的一千行使用相同的数组

如果上面的伪代码解决了我的问题,那么实际上 String 类的不可变特性会伤害 Java 程序员,因为即使 String 不再使用,也没有直接的方法来声明 String 使用的空间。

有没有更好的办法来解决这个问题?

P.S:我没有单独进行静态分析。我使用 yourkit profiler 来测试堆转储。转储清楚地表明 96% 的字符串没有 GC 根,这意味着它们正在等待收集垃圾。另外我不在我的代码中使用 Substring。

【问题讨论】:

  • 您应该先发布您的代码。我怀疑您有泄漏,这意味着您以某种方式保留了对您已经处理过的字符串的引用(写入数据库)。如果您不再引用它们,Java 的 GC 将确保它处理对象(包括字符串)。您的内存不足问题来自其他地方。
  • 仅仅通过静态分析和你的OutOfMemoryError证明你确实有内存泄漏,很难找出程序是否存在内存泄漏。但是,如果没有您的代码,除此之外将没有任何有用的建议。
  • 不,我没有单独进行静态分析。我使用 yourkit profiler 来测试堆转储。转储清楚地表明 96% 的字符串没有 GC 根,这意味着它们正在等待收集垃圾。
  • 字符串本身就是空壳。真正的问题是他们的内部 char 数组发生了什么。这些在字符串实例之间共享,因此您的实时字符串实例很有可能保留旧的 char 数组。如果您保存来自substringtrim 或其他调用输入字符串的方法的字符串,就会发生这种情况。
  • 我认为铁的事实是没有人会满足于仅仅断言您已经排除了内存泄漏。我们想查看代码,或其他重现相同问题的代码。

标签: java memory memory-management out-of-memory


【解决方案1】:

String 类的不变性与OutOfMemoryError 完全无关。不变性意味着它永远不会改变,只能这样。

如果你的内存用完了,那只是因为 garbage collector 无法找到任何 garbagecollect

在实践中,您可能在内存中保存了对太多字符串的引用(例如,您是否有任何类型的集合保存字符串,例如 List、Set、Map?)。您必须销毁这些引用以允许垃圾收集器完成其工作并释放一些内存。

【讨论】:

  • 我在我的问题中添加了 PS。
【解决方案2】:

这个问题的简单答案是“不”。我怀疑你挂在参考文献上的时间比你想象的要长。

您是否正确关闭了这些流?你是intern()ing 那些字符串吗?如果字符串尚不存在,这将导致永久复制字符串,并占用 permgen 空间(未收集)。您是否正在使用较大字符串的substring()?字符串使用享元模式,如果使用substring() 创建,将共享一个字符数组。详情请见here

您建议垃圾收集未运行。选项-verbose:gc 将记录垃圾收集,您可以立即看到发生了什么。

【讨论】:

    【解决方案3】:

    唯一可能导致 OutOfMemoryError 的字符串是如果您保留了更大字符串的一小部分。如果您这样做,从堆转储中应该很明显。

    当您进行堆转储时,我建议您只查看活动对象,在这种情况下,您不需要的任何保留对象很可能是您代码中的错误。

    【讨论】:

    • 我没有单独做静态分析。我使用 yourkit profiler 来测试堆转储。转储清楚地表明 96% 的字符串没有 GC 根,这意味着它们正在等待收集垃圾。
    • 我不在任何地方使用 Substring :-)
    • 如果您有等待清理的对象,它们将在下一次 GC 时被收集,并且不会触发 OutOfMemroyError。如果这是您关心的问题,您应该只转储活动对象(在 Full GC 之后)
    • 很难相信堆上等待垃圾回收的对象不会导致内存不足。为什么这么说?
    • Full GC总是在堆触发 OutOfMemoryError 之前运行。
    猜你喜欢
    • 2014-06-07
    • 1970-01-01
    • 2018-01-03
    • 2012-08-12
    • 2012-02-03
    • 2015-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多