AbstractList$SubAbstractList.get

2018-11-26  本文已影响0人  Yagami3zZ

在项目开发过程中,遇到了这个崩溃,花费了了很多时间排查,仅以此文记录解决问题的思路和方式,以免后人再次踩坑。

  java.lang.StackOverflowError
        at java.util.AbstractList$SubAbstractList.get(AbstractList.java:292)
        at java.util.AbstractList$SubAbstractList.get(AbstractList.java:292)
        at java.util.AbstractList$SubAbstractList.get(AbstractList.java:292)
        at java.util.AbstractList$SubAbstractList.get(AbstractList.java:292)
        at java.util.AbstractList$SubAbstractList.get(AbstractList.java:292)
        at java.util.AbstractList$SubAbstractList.get(AbstractList.java:292)
        at java.util.AbstractList$SubAbstractList.get(AbstractList.java:292)
        at java.util.AbstractList$SubAbstractList.get(AbstractList.java:292)

该问题触发的条件:

Oppo R7s Android 4.4.4系统。
播放一条超过15s的音频的时候,在播放第二遍时,第12s会崩溃,且只有这一台手机有问题。

最后通过查资料,以及和@Cavabiao一起排查,发现是使用ArrayList的subList方法时触发的问题。

以下面这段代码为例(不想找项目代码了,一样的逻辑)

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("");  
    for (int i = 0; i < 50000; i++) {
        list = list.subList(0, 1);
    }
    list.add("test");        
}

list = list.subList(0, 1); 该方法会触发递归, 当subList()方法调用且外面再加上一个循环的时候,最早的ListB= ListA.subList(),ListB持有他自己的Parent也就是ListA的引用, ListC= ListB.subList()。 以此循环 List50000= List49999.subList() 这样, 在List50000 调用自己的add remove 或者get方法时, 这几个方法里面,都需要调用其parent 来实现逻辑。那么以 List50000的 get方法为例,该get方法就需要以此递归调用List49999 List49998,List49997...ListC,ListB,LIstA 的get方法,这么多的递归调用极易造成栈溢出。

不过不同API Level的源码实现是不一样的,我们仅以API Level25 和API Level21 为例。

API Level25:

1)ArrayList.java:

1.png 2.png 3.png

可以看出ArrayList 类中,有个SubList的子类,
1)其里面的get set方法 只用到了elementData ,没有用到parent ,因为应该不会有递归调用的问题(未测试验证)
2) 其remove add方法,里面用到了parent的引用,因此会存在递归调用引发栈溢出的问题。

2)AbstractList.java:

4.png 5.png

AbstractList.java中 的SubList类中的 set get add remove等方法,均使用到parent(即l) 会存在递归调用引发栈溢出的问题。

API level21:

1)ArrayList.java:

没有SubList这个类

2)AbstractList.java:

6.png 7.png

可以看到AbstractList 中有个SubAbstactList类,该类的add addAll get remove set方法等,都用到了
parent的引用(即fullList) 那么这些方法会存在递归调用引发栈溢出的问题。

总结:

1)API Level 21 (Android 5.0) 23(Android6.0)(根据源码验证)
ArrayList 本身并没有实现SubList内部类,其subList()方法是直接使用父类AbstractList 中的SubList类实现
其set get add remove 方法中均用到了parent的引用,导致递归调用,易引发StackOverFlow的问题

2)API Level 25 (Android 7.1)(根据源码验证)
ArrayList 本身有实现SubList内部类,其subList()方法是直接使用ArrayList 中的SubList类实现,
而不是AbstactList 中的SubLIst类实现,所以
其set get 方法没有用到parent的引用,不会导致递归调用, 而其 add remove方法,用到了parent的引用,导致递归调用,易引发StackOverFlow的问题

3)其他的版本,比如API Level 22 (Android 5.1) API Level 24 (Android 7.0),因为没去看源码,故在此不做评论,但是依据代码的版本管理策略, API Level 22 (Android 5.1)应该和API Level 21 (Android 5.0)一致,而API Level 24 (Android 7.0)应该和 API Level 25 (Android 7.1) 一致,感兴趣的同学可以自行查阅。

因时间关系文章难免有疏漏,欢迎提出指正,谢谢。特别感谢@Cavabiao的协助排查。

参考文章:
1、慎用subList:ArrayList$SubList.add导致的java.lang.StackOverflowError

上一篇下一篇

猜你喜欢

热点阅读