许久没水文章了,今日忽然想水水。给我们分享一下一年多曾经遇到的一个无限缓存的 OOM 现象
首要
想必我们都知道OOM
是啥吧,我就不扯花里胡哨的了,直接进入正题。先说一个背景故事,我司app扫码框架用的zxing
,在很长一段时间曾经,做过一系列的扫码优化,稍微列一下跟今日主题相关的改动:
- 串行处理改成并发处理,
zxing
的原生处理流程是通过CameraManager
获取到一帧的数据之后,通过DecodeHandler
去处理,处理完结之后再去获取下一帧,咱们给改成了线程池去调度:
- 单帧
decode
使命入行列之后当即获取下一帧数据 - 二维码辨认成功则中止其他解析使命
- 为了有更大的辨认区域,挑选对整张拍摄图片进行解码,确保中心框框没对准二维码也能辨认到
现象
当时测试反馈,手上一个很古老的 Android 5.0
的机器,翻开扫一扫必崩,一看错误栈,是个OOM
。
机器找不到了,我就不贴现象的仓库了(埋在韶光里了,懒得挖了)。
排查OOM三板斧
板斧一、 通过一定手法,抓取崩溃时的或许崩溃前的内存快照
咦,一年前的hprof
文件还在?的确被我找到了。。。
从图中咱们能取得哪些信息?
-
用户
OOM
时,byte
数组的java
堆占用是爆炸的 -
用户
OOM
时,byte
数组里,有大量的3M
的byte
数组 -
3M
的byte
数组是被zxing
的DecodeHandler$2
引用的
板斧二、从内存对照动身,斗胆猜想找到坏死本源
咱们已然知道了 大目标 是被 DecodeHandler$2
引用的,那么 DecodeHandler$2
是个啥呀?
mDecodeExecutor.execute(new Runnable() {
@Override
public void run() {
for (Reader reader : mReaders) {
decodeInternal(data, width, height, reader, fullScreenFrame);
}
}
});
所以稍微滚动一下脑瓜子就能知道,必定是堆积了太多的 Runnable
,每个Runnable
持有了一个 data
大目标才导致了这个OOM
问题。
可是为啥会堆积太多 Runnable
呢?结合一下只有 Android 5.0
机器会OOM
,咱们斗胆猜想一下,便是因为这个机器消费(或许说解码)单张 Bitmap
太慢,同时像上面所说的,咱们单帧decode
使命入行列之后当即获取下一帧数据并入队下一帧decode
使命,这就导致大目标堆积在了LinkedBlockingDeque
中。
OK,到这里原因也清楚了,改掉就完事了。
板斧三、 吃个口香糖舒缓一下心情
呵呵…
处理方案
处理方案其实很简单,从问题动身即可,问题是啥?我出产面包速度是一天10个,一个一斤,可是一天只能吃三斤,那岂不就一天就会多7斤囤货,假如囤货到了100斤地球会毁灭,怎样处理呢?
- 吃快点,一天吃10斤
- 少出产点,要么出产个数削减,要么出产单个分量削减,要么二者一起
- 出产前检查一下吃完没,吃完再出产都来得及,真实不行定个阈值觉得不行吃了再出产嘛。
那么自然而然的就大概知道有哪几种处理办法了:
- 出产的小点 – 隔几帧插一张全屏帧即可(假如要保存不在框框内也能解码的特性的话)
- 出产前检查一下吃完没 – 线程池的线程闲暇时,才去
enqueue decode
使命 - 出产单个分量削减 – 约束行列大小
- blalala
总结
装腔作势的总结一下。
这个比如是一年前遇到的,今日想水篇文章又忽然想到了这个事就拿来写写,我总结为:线程池调度 + 进堵塞行列单使命数据过大 + 处理使命过慢
线程池调度使命是啥场景?
- 有个
Queue
,来了使命,先入队 - 有个
ThreadPool
,闲暇了,从Queue
取使命。
那么,当入队的数据结构占内存太大,且 ThreadPool
处理速度小于 入队速度呢?就会形成 Queue
中数据越来越多,直到 OOM
。
扫一扫完美的满意了上面条件
-
入队频率足够高
-
入队目标足够大
-
处理速度足够慢。
在这个比如中,做的不足的地方:
-
追求并发未考虑机器功能
-
大目标处理不行谨慎
当然,总结是为了避免未来相同的惨案发生,我们可以想想还会有什么相似的场景吧,滚动一下聪明的小脑袋瓜~
未来展望
装腔作势展望一下,未来展望便是,今后有空多水水贴子吧(不是多水水贴吧)。