AbstractList$SubAbstractList.get java.lang.StackOverflowError的问题分析与解决

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

  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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,598评论 25 708
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,991评论 2 59
  • 许久没有联系的朋友忽然冒泡:最近还好吗? 坦白说,收到这种略显笨拙而突兀的问候其实并不舒服,感觉像是多年不串门的邻...
    写锦文阅读 588评论 0 0
  • 风啊迷住了四野迷住了眼睛而怀里的羽毛柔软充实我和我的大白鸟一跃而下在万尺高空的悬崖没有人到过的地方还是在梦里我和我...
    葺宝阅读 201评论 0 3
  • 今天是什么日子 起床:6点。 就寝:22:30 天气:大雨转小雨 心情:平静、喜悦、祥和 纪念日:同学WYJ的儿子...
    喜羊羊_43e1阅读 232评论 0 1