List#subList和Spring#split的使用陷阱

"使用不当会引起内存泄漏!!"

Posted by Ariescat on February 15, 2019

前言

如果Java应用程序出现了内存泄露,千万别着急着把应用杀掉,而是要保存现场。如果是互联网应用,可以把流量切到其他服务器。保存现场的目的就是为了把 运行中JVM的heap dump下来。

正文

裁剪List的时候这样子使用的话就会有一个引发内存泄漏的陷阱:

    myList = myList.subList(fromIndex, toIndex);

特别是在循环调用的时候。

来看看源码一探究竟:

    public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }
    SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
        this.parent = parent;
        this.parentOffset = fromIndex;
        this.offset = offset + fromIndex;
        this.size = toIndex - fromIndex;
        this.modCount = ArrayList.this.modCount;
    }

new出的SubList里保留着一份父引用。

同样,在Stringsplit里,它最终会调用到String#substring

    new String(value, beginIndex, subLen);

这里同样传了一份父引用value进去。

是否Bug

为了避免内存拷贝、加快速度,Sun JDK直接复用了原String对象的char[],偏移量和长度来标识不同的字符串内容。也就是说,subString出来的String小对象 仍然会指向原String大对象的char[]split也是同样的情况。

虽然,subStringsplit的实现造成了现在的问题,但是这能否算String类的bug呢?个人觉得不好说。因为这样的优化是比较合理 的,subStringspit的结果肯定是原字符串的连续子序列。只能说,String不仅仅是一个核心类,它对于JVM来说是与原始类型同等重要的 类型。

JDK实现对String做各种可能的优化都是可以理解的。但是优化带来了忧患,我们程序员足够了解他们,才能用好他们。

参考

分析内存泄露的一般步骤


喜迎
春节