【问题标题】:Java substring operation seems to cause Out of Memory Error in java 1.8Java 子字符串操作似乎导致 Java 1.8 中的内存不足错误
【发布时间】:2018-01-03 16:53:08
【问题描述】:

这是对我们已经切换到 1.8 后导致问题的一些生成代码的提炼。

谁能帮助解释为什么它在 Java 1.6 中编译和运行,但在 1.8 中导致内存不足错误?此外,如果您注释掉 set.add(s1) 行,它似乎在 1.8 中运行良好。

我很确定这不是因为我将 5 个字符的子字符串存储在一个集合中。它应该能够处理其中的 12,000 个。另外,它在 1.6 中有效,即使我将行更改为 set.add(new String(s1))set.add(s1 + " ") 以尝试强制创建新字符串。

package put.your.package.here;

import java.util.HashSet;
import java.util.Set;

public class SubstringTest {

    public static void main(String[] args) {
        String s = buildArbitraryString();
        System.out.println(System.getProperty("java.version") + "::" + s.length());
        Set<String> set = new HashSet<String>();
        while (s.length() > 0) {
            s = whackString(s, set);
        }
    }

    private static String whackString(String s, Set<String> set) {
        String s1 = s.substring(0, 5);
        String s2 = s.substring(5);
        s = s2;
        set.add(s1);
        System.out.println(s1 + " :: " + set.size());
        return s;
    }

    private static String buildArbitraryString() {
        StringBuffer sb = new StringBuffer(60000);
        for (int i = 0; i < 15000; i++)
            sb.append(i);
        String s = sb.toString();
        return s;
    }
}

有什么想法吗?

JVM 版本信息:

java.vm.name=IBM J9 VM
java.fullversion=
    JRE 1.8.0 IBM J9 2.8 Windows 7 amd64-64 Compressed References 20160210_289934 (JIT enabled, AOT enabled)
    J9VM - R28_Java8_SR2_20160210_1617_B289934
    JIT  - tr.r14.java_20151209_107110.04
    GC   - R28_Java8_SR2_20160210_1617_B289934_CMPRSS
    J9CL - 20160210_289934

已编辑以添加 JVM 信息

【问题讨论】:

  • 您使用的是哪个 JVM?它在 Hotspot VM 上对我来说很好。还有你的 JVM 启动参数是什么?请注意,1.8 中的某些参数(如 permgen 配置)已消失。
  • 你从哪里来? stackoverflow.com/q/33893655/995891 会产生相当大的影响。
  • 是时候启动堆分析器了。
  • 使用-XX:+HeapDumpOnOutOfMemoryError 开关启动JVM,然后我建议尝试MAT

标签: java string out-of-memory substring


【解决方案1】:

好的,我们进行了更多的挖掘工作,我们认为我们已经找到了问题所在。 在 WAS/IBM Java 1.6 实现中,子字符串调用如下所示:

return ((beginIndex == 0) && (endIndex == count)) ? this :
    new String(offset + beginIndex, endIndex - beginIndex, value);

我们使用调试器对此进行了验证。每个新字符串都使用具有不同偏移量和计数的相同主数组。像魅力一样工作。

在我们拥有的 WAS/IBM Java 1.8 版本中,子字符串调用如下所示:

if (!disableCopyInSubstring) {
    return new String (offset + start, end - start, value, false);
} else {
    return new String (offset + start, end - start, value);
}

disableCopyInSubstring 标志始终为 false,这是有道理的。我们不想禁用将数据复制到新数组中。该复制应该解决反复使用相同的字符数组导致的内存泄漏。这意味着substring 调用以下构造函数(为简洁而编辑):

if (start == 0) {
    value = data;
} else {
    value = new char[length];
    System.arraycopy(data, start, value, 0, length);
}
offset = 0;
count = length;

所以,基本上,如果子字符串的开头是'0',它会保留整个原始字符数组。出于某种原因,如果start 为'0',它会忽略修复内存泄漏。故意。这是两全其美的。

所以,是的。在我们的程序中,我们做 0-5 的子串,因为当start 为 0 时,这个实现不会创建一个新数组,所以它存储了整个计数长度为 5 的巨型数组。然后我们做第二个子串,删除前 5 个字符。这确实为新字符串创建了一个新数组。然后,在下一个循环中,我们再次执行短子字符串,将 整个 巨型字符串的副本减去五个字符,然后再删除五个字符并创建一个新字符串。

我们一遍又一遍,每次都存储一个稍微短一点的字符串的完整副本,只是消耗内存。

解决方案是用new String() 包围substring(0,5) 调用。我这样做了,它在这个测试用例上就像一个魅力。但是我们正在处理一个生成的类,我们无权访问生成器,所以这不是我们的选择。

编辑:戴尔发现了这个

/**
 * When the System Property == true, then disable copying in String.substring (int) and
 * String.substring (int, int) methods whenever offset is non-zero. Otherwise, enable copy.
 */
String disableCopyInSubstringProperty = getProperty("java.lang.string.substring.nocopy"); //$NON-NLS-1$
String.disableCopyInSubstring = disableCopyInSubstringProperty != null && 
    disableCopyInSubstringProperty.equalsIgnoreCase("true"); //$NON-NLS-1$

【讨论】:

  • 使用一个参数调用子字符串会从该索引返回到字符串的末尾。所以调用.substring(0) 只是复制字符串。 Quote: 由于某种原因,如果开始是'0',它忽略了修复内存泄漏。如果你想从第0个位置到字符串的结尾,1.8中没有内存泄漏你只会得到相同的字符串。
  • 没关系,我没听懂你在说什么。
  • 设置如下系统属性解决了问题 -Djava.lang.string.substring.nocopy=true 还要注意的是这是IBM对Java的实现
【解决方案2】:

我没有完整的答案,但我无法发表评论,因为我没有足够的学分。
您应该阅读以下帖子中的答案: substring method in String class causes memory leak

它解释了子字符串的实现发生了变化。我认为你应该检查 wackString 方法返回的大子字符串的影响,垃圾收集是否足够快地清理这些子字符串,因为新的子字符串实现会消耗更多的内存。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-06
    • 2016-05-19
    • 1970-01-01
    • 2012-01-11
    • 1970-01-01
    • 2011-05-05
    • 1970-01-01
    相关资源
    最近更新 更多