【问题标题】:Java String.substring method potential memory leak?Java String.substring 方法潜在的内存泄漏?
【发布时间】:2012-12-19 02:56:03
【问题描述】:

我正在查看 String 类 API,并且看起来存在由 substring 方法引起的潜在内存泄漏,因为它与原始 String 共享相同的字符数组。

如果原始字符串很大,那么子字符串返回的小字符串可以防止原始字符串(由大数组备份)在Java中进行垃圾回收。

任何想法或我是否读错了 API。

【问题讨论】:

  • 这在技术上根本不是内存泄漏,因为字符数组仍然被引用,并且可以在收集所有引用它的字符串时收集。部分字符数组可能不再使用,但这不会导致泄漏。
  • 如果每个 100MB 有 100 个大字符串,并且有一个子字符串 (0,1),则从技术上讲,您持有 String 类中使用的 value[] 并且在您的应用程序中永远不会有大字符串有资格成为垃圾收藏

标签: java string memory-leaks


【解决方案1】:

如果您获取一个相当大的字符串的子字符串而不进行复制(通常通过String(String) 构造函数),则存在内存泄漏的可能性。

请注意,自 Java 7u6 以来,这已更改。 见https://bugs.openjdk.java.net/browse/JDK-7197183

围绕实现flyweight patternString 对象的原始假设不再被视为有效。

请参阅this answer 了解更多信息。

【讨论】:

  • 两种策略都有效。原来的 impl 很好;新的 impl 很好。但是更改 impl 并不好……这是兼容性的惊人突破。由于明显的原因,他们不可能做到这一点。还有其他事情发生。
  • @irreputable 诚然,这是一个不属于规范的实现细节 => 不应该依赖它。出于性能原因进行了更改。
  • @assylias 这更像是一种逃避。人们已经建立了 substring()/trim() 是 O(1) 的信念。默默地将其更改为 O(n) 不是很好。以及如何编写在不同 Java 版本上表现一致的代码?
  • 他们这样做是出于性能原因,但不是明显的,它们非常薄弱。
  • @irreputable 他们这样做是为了总体上提高字符串的性能,但缺点是子字符串的性能已经恶化......
【解决方案2】:
  1. 在 Java 7u6 之前就是这种情况 - 您通常会通过以下方式处理该问题:

    String sub = new String(s.substring(...)); // create a new string
    

    这有效地消除了依赖,原始字符串现在可用于 GC。顺便说一句,这是使用字符串构造函数有意义的唯一场景之一。

  2. 由于Java 7u6,创建了一个新的String,不再存在内存问题。

【讨论】:

  • 是的,但它会产生一个新问题。如果你 trim() 关闭一个空格,这是一种非常常见的情况,你最终会复制 N-1 字符。
  • @irreputable 你总能找到表现更差的极端情况。通用图书馆的目标是平均表现良好,并且在做出改变之前似乎已经考虑了许多不同的用例。
  • 复制字符也非常快(不是说没有成本)。
  • trim() 不是极端情况。其实str.substring().trim()是一个很常见的情况,涉及到复制两次。 O(1)->O(n) 很重要。不,oracle 不知道这将如何影响现有的应用程序。
  • @irreputable 我理解你的意思。有许多变量需要考虑(特别是在这种情况下更好地使用 CPU 的缓存)即使您经常使用子字符串也可以平衡这一点。您可以在the discussions 中挖掘以获取更多信息。
【解决方案3】:

在 Java 7 中,String 的 subString 被修改为:

/**
     * Returns a new string that is a substring of this string. The
     * substring begins with the character at the specified index and
     * extends to the end of this string. <p>
     * Examples:
     * <blockquote><pre>
     * "unhappy".substring(2) returns "happy"
     * "Harbison".substring(3) returns "bison"
     * "emptiness".substring(9) returns "" (an empty string)
     * </pre></blockquote>
     *
     * @param      beginIndex   the beginning index, inclusive.
     * @return     the specified substring.
     * @exception  IndexOutOfBoundsException  if
     *             <code>beginIndex</code> is negative or larger than the
     *             length of this <code>String</code> object.
     */
    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

因此,每次你在 beginIndex 不等于 0 的情况下执行 subString,我们都有新的 String 对象。

【讨论】:

    猜你喜欢
    • 2011-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多