问题现象描述
- Android机器上面突然出现该crash,还原到以前版本无法复现,并且后续的版本也没有出现该问题现象。
- Native代码创建线程栈大小默认为64KB,native代码会通过JNI来调用java代码实现逻辑,栈信息由于java 进程crash进而只显示java部分代码的调用栈信息
java.lang.StackOverflowError: stack size 65KB at java.nio.Buffer.<init>(Buffer.java:209) at java.nio.ByteBuffer.<init>(ByteBuffer.java:224) at java.nio.MappedByteBuffer.<init>(MappedByteBuffer.java:91) at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:140) at java.nio.DirectByteBuffer.slice(DirectByteBuffer.java:168) at android.icu.impl.ICUBinary.sliceWithOrder(ICUBinary.java:697) at android.icu.impl.ICUBinary$DatPackageReader.getData(ICUBinary.java:102) at android.icu.impl.ICUBinary$PackageDataFile.getData(ICUBinary.java:275) at android.icu.impl.ICUBinary.getDataFromFile(ICUBinary.java:512) at android.icu.impl.ICUBinary.getData(ICUBinary.java:486) at android.icu.impl.ICUBinary.getData(ICUBinary.java:441) at android.icu.impl.ICUResourceBundleReader$ReaderCache.createInstance(ICUResourceBundleReader.java:190) at android.icu.impl.ICUResourceBundleReader$ReaderCache.createInstance(ICUResourceBundleReader.java:179) at android.icu.impl.SoftCache.getInstance(SoftCache.java:71) at android.icu.impl.ICUResourceBundleReader.getReader(ICUResourceBundleReader.java:234) at android.icu.impl.ICUResourceBundle.createBundle(ICUResourceBundle.java:1292) at android.icu.impl.ICUResourceBundle$4.load(ICUResourceBundle.java:1165) at android.icu.impl.ICUResourceBundle$1.createInstance(ICUResourceBundle.java:96) at android.icu.impl.ICUResourceBundle$1.createInstance(ICUResourceBundle.java:93) at android.icu.impl.SoftCache.getInstance(SoftCache.java:71) at android.icu.impl.ICUResourceBundle.instantiateBundle(ICUResourceBundle.java:1152) at android.icu.impl.ICUResourceBundle.access$600(ICUResourceBundle.java:39) at android.icu.impl.ICUResourceBundle$4.load(ICUResourceBundle.java:1213) at android.icu.impl.ICUResourceBundle$1.createInstance(ICUResourceBundle.java:96) at android.icu.impl.ICUResourceBundle$1.createInstance(ICUResourceBundle.java:93) at android.icu.impl.SoftCache.getInstance(SoftCache.java:71) at android.icu.impl.ICUResourceBundle.instantiateBundle(ICUResourceBundle.java:1152) at android.icu.impl.ICUResourceBundle.getBundleInstance(ICUResourceBundle.java:1128) at android.icu.impl.ICUResourceBundle.getBundleInstance(ICUResourceBundle.java:1113) at android.icu.impl.coll.CollationLoader.loadTailoring(CollationLoader.java:112) at android.icu.text.CollatorServiceShim.makeInstance(CollatorServiceShim.java:185) at android.icu.text.CollatorServiceShim.access$000(CollatorServiceShim.java:30) at android.icu.text.CollatorServiceShim$CService$1CollatorFactory.handleCreate(CollatorServiceShim.java:143) at android.icu.impl.ICULocaleService$LocaleKeyFactory.create(ICULocaleService.java:388) at android.icu.impl.ICUService.getKey(ICUService.java:462) at android.icu.impl.ICUService.getKey(ICUService.java:389) at android.icu.impl.ICULocaleService.get(ICULocaleService.java:77) at android.icu.impl.ICULocaleService.get(ICULocaleService.java:61) at android.icu.text.CollatorServiceShim.getInstance(CollatorServiceShim.java:40) at android.icu.text.Collator.getInstance(Collator.java:797) at android.icu.text.Collator.getInstance(Collator.java:826) at java.text.Collator.getInstance(Collator.java:236)
分析内容
StackOverflowError如下情况会出现:
-
函数实现中存在不合法的递归调用
->查看代码并没有符合逻辑的操作,并且单体测试相关内容都测过,并没有重复调用的可能出现
->根据crash栈调用的情况来看,初步怀疑可能是源码递归造成,通过但是在正常情况下调用的次数和抛异常的时候调用的次数一致,排除掉该情况。 -
栈变量分配空间过多,超过设置栈的大小
->当前线程栈空间不足,通过获取栈使用大小方法来确认当前线程栈动态使用,来判定是否超过线程栈限定大小
结果
crash现象原因: native开启的默认线程64KB在调用 AOSP的时候分配空间不够。
调用接口
at android.icu.impl.ICUBinary.sliceWithOrder(ICUBinary.java:697) //64KB
at java.text.Collator.getInstance(Collator.java:236) // 20KB
解决办法
- 通过接口设置线程栈的最大值
- 通过抛线程的方式让其它线程栈最大值大于64KB大小的线程处理
由于方法1的设置线程栈在项目中没有提供接口,故采用方法2进行抛线程方式来处理,最终通过sendMessage的方式将当前线程抛给handler使用的线程中处理
调查使用方法
栈使用大小获取方法
Java进程获取线程栈方法:
1.断点打印到指定调用栈
2.使用kill -3 <attch process id> 在/data/anr中查看trace 文件,通过指定调用栈函数作为关键字找到进程,获取到栈首地 址
3.栈首地址作为关键字,在 /proc/< attch process id >/smaps 筛选来获取栈的使用量大小
cat /proc/< attch process id >/smaps | grep –A 4 –B 1 <栈首地址>, 然后查看RSS即为当前栈的分配大小
4.通过调试来确定每层接口调用的情况下,当前栈的分配大小
Native 进程获取线程栈办法:
1.线程开始定义一个变量,打印变量地址, 栈起始大小A
2.线程运行到某处,打印此时临时变量的地址, 栈大小B
3.使用栈的起始大小和当前栈大小做差值算出, |A-B|即为使用量大小
源码无法使用log确认方法:
-
在源码中指定位置添加
new Exception("test print stack").printStackTrace();
-
然后运行过程中使用logtag来确认调用栈情况
LogTag: System.err
采用工具
调查java层代码:Android Studio 通过attch进程的方式来打断点来查看调用栈
调查native层代码: Android版本支持gdb,采用gdb attch 进程的方式同样打断点来查看调用栈
另外Android Studio 有个工具profilter可以分析调用过程中cpu/memory/network/energy 运行的动态情况
各目录存放信息整理
Android里面由于可以自定义存储位置和方法,本次记录为自身项目中信息描述
文件 | 信息 |
---|---|
/data/system/dropbox/system_app_crach@< timestamp > | java层crash信息 |
/data/tombstone/tombstone_< number > | native层墓碑信息,包含内存信息和此时发生墓碑的log |
/data/anr/trace_< number > | 无响应进程资源信息,本次配合kill -3 来使用查看栈地址 |
/proc/< pid >/smaps | 当前pid下内存使用信息,查看线程栈使用大小 |
部分问题猜测
下面问题希望有大佬看到解惑一下
1.为啥断点调试的时候会复现平常运行的时候不会复现呢?
a.断点调试的运行过程本质上是运行到断点的时候,会将原来的运行环境暂停,然后给中断信号
b.运行提前注册在进程中的调试程序,运行期间由于运行到了额外的调试接口可能会使用到当前线程栈空间导致crash复现。
2.为啥会当时出现crash现象呢,并且使用原来的环境无法复现呢?
a.可能特殊操作导致线程栈分配比平时多一点,导致调用接口的时候超过限定值大小
以上描述如存在问题,欢迎各位指正和探讨~