[转]File Descriptor泄漏导致Crash: Too many open files
在实际的Android开发过程中,我们遇到了一些奇奇怪怪的Crash,通过sigaction再配合libcorkscrew以及一些第三方的Crash Reporter都捕获不到发生Crash的具体信息,十分头疼。然后我们通过Bugly上报的Java的CallStack观察发现这些Crash发现了一些共同的信息:

看来是和OpenGL有关系,于是我们进一步对程序输出的log进行观察,又发现:

从这个log里面我们获得了几个信息:
- 几乎所有出现这种Crash的设备,都是Adreno的GPU
- 几乎所有Crash都会伴随着requestBuffer failed
我们对我们已有的设备反复试验,确实了只有Adreno的设备(小米3,HTC M8,华为P7等)会在特定条件下出现这种奇奇怪怪的随机Crash。而其他设备例如小米Pad(Tegra),三星S3(Mali)等都不会出现这种问题。这个问题确实头疼,在网上搜索了很久也没找到有用的信息。直到在某次小米3上再次测试的时候,发现了log里面还有一条必然出现的信息:
E/MemoryHeapBase(18703): error creating ashmem region: Too many open files
这个信息间接的指出了问题,也给了我们一些提示:似乎打开了过多的文件。于是靠着这个灵光,我们尝试着在程序中输出所有已打开的文件:
SHOW FILE HANDLES:
0 (socket:[285038]): read-write
1 (/dev/null): read-write
2 (/dev/null): read-write
3 (/dev/log/main): cloexec write-only
4 (/dev/log/radio): cloexec write-only
5 (/dev/log/events): cloexec write-only
6 (/dev/log/system): cloexec write-only
7 (/sys/kernel/debug/tracing/trace_marker): write-only
8 (/dev/__properties__):
9 (/dev/binder): cloexec read-write
10 (/dev/log/main): cloexec write-only
11 (/dev/log/radio): cloexec write-only
12 (/dev/log/events): cloexec write-only
13 (/dev/log/system): cloexec write-only
14 (/system/framework/framework-res.apk):
15 (/system/framework/core-libart.jar):
16 (pipe:[282578]): nonblock
17 (/dev/alarm):
18 (/dev/cpuctl/tasks): cloexec write-only
19 (/dev/cpuctl/bg_non_interactive/tasks): cloexec write-only
20 (socket:[282569]): read-write
21 (pipe:[282570]):
22 (pipe:[282570]): write-only
23 (pipe:[282578]): nonblock write-only
24 (anon_inode:[eventpoll]): read-write
25 (/data/app/---app_name---/base.apk):
26 (/data/data/---app_name---/databases/bugly_db): cloexec read-write
27 (socket:[285047]): read-write
28 (anon_inode:mali-8938): cloexec
29 (socket:[282605]): nonblock read-write
30 (socket:[283605]): nonblock read-write
31 (/dev/null): read-write
32 (/dev/ump): read-write
33 (socket:[285045]): nonblock read-write
34 (/dev/null): read-write
35 (/dev/mali): read-write
36 (anon_inode:mali-8938): cloexec
37 (anon_inode:mali-8938): cloexec
38 (/data/app/---app_name---/base.apk):
39 (anon_inode:mali-8938): cloexec
40 (anon_inode:mali-8938): cloexec
41 (/dev/null): read-write
42 (/dev/null): read-write
43 (/data/app/---app_name---/base.apk):
44 (/dev/null): read-write
45 (anon_inode:mali-8938): cloexec
46 (/data/data/---app_name---/files/DefaultFont.ttf):
47 (/data/app/---app_name---/base.apk):
48 (anon_inode:sync_fence):
49 (/dev/null): read-write
50 (socket:[285060]): cloexec read-write
52 (anon_inode:mali-8938): cloexec
53 (anon_inode:mali-8938): cloexec
54 (/dev/null): read-write
55 (anon_inode:sync_fence):
56 (pipe:[284134]): write-only
58 (anon_inode:sync_fence):
62 (anon_inode:sync_fence):
63 (anon_inode:sync_fence):
通过不停测试程序,发现已打开的文件数量一直有增无减,而当这些被打开的文件数量接近1024的时候,上面的eglSwapBuffers必然出错。于是乎我们得出一个中间结论:
- 如果程序打开的文件数量过多,会导致OpenGL swap buffer失败!
这从字面上看着似乎有些扯淡,因为这两者总感觉没啥联系。这个问题只会出现在Adreno的GPU上面,于是我们猜想:
- Adreno的驱动在swap buffer的时候,需要申请新的FD,这个FD可能是某些硬件IO,具体不得而知;
- 如果程序中其他的各种FD使用过多接近上限,会导致Adreno的驱动申请不到必要的FD,因此导致swap buffer失败。
这样看起来似乎就比较有道理了。虽然sawp buffer本身是不会Crash的,他并没有raise任何signal,只是简单的返回了一个错误的结果,但这会导致上层逻辑出现异常。这些异常在不同的设备上表现不一样:
- 有的设备会在Java层的eglSwapBuffers触发Java层的Exception导致Crash;
- 有的设备不会出现异常,但是会导致OpenGL停止工作(halt rendering),其表现结果就是程序卡住无响应;
- 有的设备可能什么都不会发生,但是如果你的交互触发了其他逻辑:比如按回退键弹出对话框,对话框也需要FD,但是获得不到,那么弹出对话框的逻辑将抛出异常,
于是这就有了各种奇奇怪怪的Crash。
解决方案
通过对代码的排查,我们发现在使用SoundPool处理音效的时候,确实存在FD泄露的情况:
1 private SoundPool m_soundPool;
2 public int loadSound(String path) {
3 int soundID = m_soundPool.load(getAssets().openFd(path), 0);
4 return soundID;
5 }
6 public unloadSound(int soundID) {
7 m_soundPool.unload(soundID);
8 }
虽然我们在不需要这些音效的时候,对其进行了卸载处理,但不知道是SoundPool类自身的缺陷,还是我们的使用不当,在实际测试中我们发现unload过后,在load中通过openFd打开的FD并没有被释放掉。强制调用System.gc()在一些设备(例如小米3)上可以释放掉这部分FD,但是另一些设备(例如HTC M8)即使强制gc这无法卸载掉它们,于是便出现了FD泄露的情况。
最终我们自行对这些FD进行管理,并且在unload的时候手动调用这些FD的close方法:
1 private SoundPool m_soundPool;
2 private HashMap<Integer, AssetFileDescriptor> m_soundFdMap
3 public int loadSound(String path) {
4 AssetFileDescriptor fd = getAssets().openFd(path);
5 int soundID = m_soundPool.load(fd, 0);
6 m_soundFdMap.put(soundID, fd);
7 return soundID;
8 }
9 public unloadSound(int soundID) {
10 m_soundPool.unload(soundID);
11 m_soundFdMap.get(soundID).close();
12 }
这之后FD再无泄露的情况发生,之前的各种设备上面的各种奇奇怪怪的Crash都被处理好了。
小结
这个问题粗略说起来就是:因为播放了太多的音效,导致Adreno底层渲染失败,以至于上层逻辑各种失措,产生了很多奇奇怪怪的Crash。准确的解释应该是:程序中的FD泄露如同内存泄露一样是同样需要得到关注的问题,FD的耗尽如同内存的耗尽一样会导致程序的各种异常情况发生,但是前者不如后者那么知名也不如后者容易被察觉。
转自:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=28
[转]File Descriptor泄漏导致Crash: Too many open files的更多相关文章
- fastDFS errcode:9 path:Bad file descriptor errcode:22 path:Invalid argument
fastDFS errcode:9 path:Bad file descriptor errcode:22 path:Invalid argument <error>status:4 er ...
- dpdk EAL: Error reading from file descriptor 23: Input/output error
执行test程序时输出: EAL: Error reading from file descriptor 23: Input/output error 原因: 在虚拟机添加的网卡,dpdk不支持导致的 ...
- 文件描述符file descriptor与inode的相关知识
每个进程在Linux内核中都有一个task_struct结构体来维护进程相关的 信息,称为进程描述符(Process Descriptor),而在操作系统理论中称为进程控制块 (PCB,Process ...
- 出现epoll failed: Bad file descriptor的原因
今天遇到了这个问题,之前找了半天原来是IO事件的socket描述符在epoll_ctl()处理之前关闭了. if(epoll_ctl(epollFd, EPOLL_CTL_DEL, ev->fd ...
- java.net.SocketException: recvfrom failed: EBADF (Bad file descriptor)
1. 问题说明: 与服务器之间进行socket通信的时候,客户端关闭socket之后,会抛出一个IOException,异常信息如下: java.net.SocketException: recvfr ...
- 无法获取指向控制台的文件描述符 (couldn't get a file descriptor referring to the console)
背景 最近收拾东西,从一堆杂物里翻出来尘封四年多的树莓派 3B 主机来,打扫打扫灰尘,接上电源,居然还能通过之前设置好的 VNC 连上.欣慰之余,开始 clone 我的 git 项目,为它们拓展一个新 ...
- 近期编程问题——epoll failed:bad file descriptor
出现问题:epoll_wait:Bad file descriptor 原因:IO时间的socket描述符在epoll_ctl处理前就关闭了. 解决方法:不要在epoll_ctl之前关闭socket描 ...
- mongodb在ubuntu下的couldn‘t remove fs lock errno:9 Bad file descriptor的错误
按照官网上的安装方法: 在ubuntu系统下有可能出现如下错误: couldn't remove fs lock errno:9 Bad file descriptor 此时需要修改文件所有者 $ s ...
- 用dup2和dup产生一份file descriptor 的拷贝
在类Unix操作系统里面,.dup2和dup都通过系统调用来产生一份file descriptor 的拷贝. dup对我来说还很简单 int dup(int filedes); dup2就 ...
随机推荐
- Hibernate注解
前言: 最近正在学习Hibernate通过注解(annotation)来管理映射关系,以前都是通过XML映射文件.下面拿个小例子说一下. 数据库物理模型: 数据库的描述: 一篇博客随笔可以分到不同的类 ...
- Boot from Volume - 每天5分钟玩转 OpenStack(61)
Volume 除了可以用作 instance 的数据盘,也可以作为启动盘(Bootable Volume),那么如何使 volume 成为 bootable 呢? 现在我们打开 instance 的 ...
- wireshark常用的过滤器设置
过滤源ip.目的ip.在wireshark的过滤规则框Filter中输入过滤条件.如查找目的地址为192.168.101.8的包,ip.dst==192.168.101.8:查找源地址为ip.src ...
- easyui-datagrid 列单击事件
首先要注意的就是,先添加一个js方法,名字可以自定义,但是必须得与下面的option里面的onClickRow:后面的一致即可 <script type="text/java ...
- mysql悲观锁总结和实践--转
原文地址:http://chenzhou123520.iteye.com/blog/1860954 最近学习了一下数据库的悲观锁和乐观锁,根据自己的理解和网上参考资料总结如下: 悲观锁介绍(百科): ...
- 在博客中使用LaTeX插入数学公式
在博客中使用LaTeX插入数学公式 在学习机器学习中会接触到大量的数学公式,所以在写博客是会非常的麻烦.用公式编辑器一个一个写会非常的麻烦,这时候我们可以使用LaTeX来插入公式. 写这篇博文的目的在 ...
- 【Java心得总结四】Java泛型下——万恶的擦除
一.万恶的擦除 我在自己总结的[Java心得总结三]Java泛型上——初识泛型这篇博文中提到了Java中对泛型擦除的问题,考虑下面代码: import java.util.*; public clas ...
- jquery兼容实验
使用jquery时候,曾经遇到两个问题: 1.一个页面里,使用了很多基于jquery的前端ui库,但是各个库之间对jquery的依赖的版本不一样,有的依赖jquery1.4,jquery2.0用不了: ...
- mysql转换类型
今天遇到一个问题,那就是当时一个项目设计表的时候,把时间以20160101123596这样的形式,以varchar存到了数据库里. 今天要写一条sql,查询一个报表,条件就是要过滤这个时间,但是var ...
- CSS-float详解,深入理解clear:both[转+部分原创]
首先要知道,div是块级元素,在页面中独占一行,自上而下排列,也就是传说中的流. 可以看出,即使div1的宽度很小,页面中一行可以容下div1和div2,div2也不会排在div1后边,因为div元素 ...