Android句柄泄露:too many open files

1.简介:


FD(File Descriptor)文件描述符作为一个索引值,用于指向进程内的打开文件。当我们在进程中,打开文件,打开网络流(socket),管道或者其他资源,都会生成文件描述符。然后每个进程中这个值都是有限制的,一般情况下为1024。


2.FD泄露场景

FD泄露与内存泄露有一定的重合,其中第2,5条容易被忽视,在编码中需要特别注意,

  • 1.数据库: Cursor使用完未关闭
  • 2.输入输出:FileInputStream,FileOutputStream,FileReader,FileWriter 等输入输出使用完未关闭
  • 3.HandlerThread: 使用完未调用quit方法。使用时,可以设置name或tag的地方,命名具体一些,便于定位(大部分基本为此泄露)
  • 4.Thread: HandlerThread实际上是带有Loop的thread,而对于传统的Java Thread,需要声明Loop以后才会出现FD的增加。因为声明Loop相当于增加了一块缓冲区,需要有一个FD来标识。如果反复调用下面这段代码也会出现FD泄漏。如果确定不需要Looper,可以使用Looper.quit()或者Looper.quitSafely()来退出looper,避免出现FD泄漏。
  • 5.WindowManager: WindowManager.addView每次调用,都会在server(WindowManagerService)和Client(用户进程)端创建fd文件来作为socket通信,如果不调用removeView这个fd将得不到释放
  • 6.socket: 其他大量使用socket通信,但未关闭的情况
  • 7.Dialog:使用完Dialog之后需要主动dismiss。否则也会造成泄露(见附1报错log)
    注:JDK7中新增了try-with-resource语法,实现closable的api,cursor或者stream,socket等,都可以使用try with语法自动关闭

4.查看指定进程的文件描述符数量限制

       $ adb shell //切换到shell环境
       # ps | grep [package_name] //查找我们想要查看的进程的PID
       # cat /proc/[进程PID]/limits //查看进程文件描述符数量最大限制

5.查看当前进程文件描述符数量

       # cd /proc/[进程PID]/fd //进入df目录下
       # ls -l | wc -l //统计文件描述符数量


6.排查方式

  • 1.对于已知的FD泄露场景可通过代码全局搜索排查
  • 2.对于不确定的可通过重复执行某一操作,查看FD是否持续增长来判断是否有FD泄露,然后有针对性的排查代码逻辑。

附:1. dialog fd泄露报错

AndroidRuntime: FATAL EXCEPTION: main
AndroidRuntime: Process: com.jamdeo.tv.vod, PID: 2110
AndroidRuntime: java.lang.RuntimeException: Could not read input channel file descriptors from parcel.
AndroidRuntime: at android.view.InputChannel.nativeReadFromParcel(Native Method)
AndroidRuntime: at android.view.InputChannel.readFromParcel(InputChannel.java:148)
AndroidRuntime: at android.view.IWindowSession$Stub$Proxy.addToDisplay(IWindowSession.java:759)
AndroidRuntime: at android.view.ViewRootImpl.setView(ViewRootImpl.java:669)
AndroidRuntime: at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:319)
AndroidRuntime: at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)

AndroidRuntime: at android.app.Dialog.show(Dialog.java:325)

7.一个例子

集成sdk后发现fd泄露特别严重,基本播放视频几十分钟之后,必现崩溃。

1.根据第五条所述,发现fd确实在一致增加。

2.ls -l | wc -l  > fd.record 输出到文件详细查看,并在不同阶段捕捉快照,如进入前,播放中,退出后,对比可回收与不可回收的文件句柄,发现以下规律:

大量的此种类型句柄类型

lrwx------ 1 root root 64 2019-12-29 10:56 126 -> anon_inode:[eventfd]
lrwx------ 1 root root 64 2019-12-29 10:56 127 -> anon_inode:[eventpoll]

较多的音频打开文件

如”1.mp3“等

3.文件句柄类型:

  • fd中如/data/data/xxxx_app/yyyy 之类的文件, 可直接定位
  • “anon_inode:[eventpoll]” 和 "anon_inode:[eventfd]", ”pipe“通常是开启了太多的HandlerThread/Looper/MessageQueue, 线程忘记关闭, 或者looper 没有释放
  • 对于system server, 如果有大批量的socket 打开, 可能是因为Input Channel 没有关闭
  • “/dev/ashmem”, 通常是ContentProvider或者数据库使用关闭情况

4.主要存在两个问题

  1. 提示音频文件 频繁打开未进行关闭
  2. 大量的“anon_inode:[eventpoll]” 和 "anon_inode:[eventfd]",代表可能有HandlerThread为调用quit或者safeQuit,一个HandlerThread正好会占用这两种文件句柄。

5.排查

  1. 使用SoundPool来进行播放提示音及伴奏的播放,每次播放都会进行load操作,频繁的load而且 未调用unload或者releaes导致大量音频提示文件被打开而未进行关闭,造成句柄泄露。
  2. 对于eventpoll和eventfd的泄露,怀疑是HandlerThread泄露引起,全面排查sdk内代码,未释放的点有几个,但不会造成大量泄露,需要继续排查

6.使用Android Studio工具Methods Record

对于捕捉到的Medhods Record进行分析,发现了大量的FileDownloader和WorkHandler的线程(对此部分可以直接通过CPU详细信息来实时查看应用的线程情况)

7.原因确定后,针对具体实现修改后验证,fd处于稳定状态,泄露修复。

 

 

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值