Android 4.4.2设备APP运行10分钟出现StackOverflowError崩溃求解决方案
根因判断
从崩溃日志可以直接得出核心结论:StackOverflowError由Collections$SynchronizedCollection.add的无限递归调用导致,栈里连续上百层相同的add调用,说明每次调用add都会触发新的add调用,栈深度持续累加直到超过Android 4.4系统的线程栈上限(默认约1MB,可容纳几百层调用),运行10分钟后触发崩溃。
排查步骤
- 全局检索代码中所有
Collections.synchronizedCollection、Collections.synchronizedSet调用点,检查被包装的集合是否为全局持有、是否被反复执行包装操作:如果将已经经过同步包装的集合对象再次传入同步包装方法,会形成多层包装嵌套,每次add都会穿透多层包装,叠加业务逻辑中的递归调用、AOP切面切入add方法后再次触发add操作,就会形成无限递归。 - 在
HashSet.add处插日志,打印调用栈,可直接定位到触发add操作的自研业务代码(日志里没有自研代码栈是因为栈溢出时栈顶都是系统类调用,完整栈被截断了)。 - 临时关闭蓝牙扫描逻辑,验证崩溃是否消失,确认是否为蓝牙扫描回调中的逻辑触发的问题。
修复方案
- 集合的同步包装操作仅在初始化时执行一次,示例如下:
// 正确:仅初始化时做一次同步包装 private final Set<String> bleDataSet = Collections.synchronizedSet(new HashSet<>()); // 错误:每次收到蓝牙数据都重复包装集合,多层包装叠加业务逻辑容易触发栈溢出 public void onBleScanResult(String data) { Set<String> tempSet = Collections.synchronizedSet(bleDataSet); tempSet.add(data); }
- 如果需要保证线程安全,也可以直接使用JDK自带的并发集合
CopyOnWriteArraySet、ConcurrentSkipListSet,不需要额外做同步包装,规避多层包装的问题。 - 如果排查发现是存入集合的自定义对象的
hashCode、equals方法中又触发了集合的add操作,调整对应逻辑,避免递归调用。
内容的提问来源于stack exchange,提问作者James Fu
相关产品推荐
相关产品推荐

