fd leak问题查找(Android)

参考:http://huzhengyu.com/2017/01/21/Fd-leak-in-Android/

https://www.ibm.com/developerworks/cn/linux/l-cn-handle/


1.句柄的介绍及应用

句柄是在 Windows 中引入的一个概念,它是和对象一一对应的 32 位无符号整数值。句柄可以映射到唯一的对象,它是处理对象的一个接口,对于所涉及的对象,可以通过相应的句柄来操作它。句柄的引入主要是操作系统为了避免应用程序直接对某个对象的数据结构进行操作为目的,用操作句柄来代替操作对象。

在 Linux 环境中,任何事物都是用文件来表示,设备是文件,目录是文件,socket 也是文件。用来表示所处理对象的接口和唯一接口就是文件。应用程序在读 / 写一个文件时,首先需要打开这个文件,打开的过程其实质就是在进程与文件之间建立起连接,句柄的作用就是唯一标识此连接。此后对文件的读 / 写时,目标文件就由这个句柄作为代表。最后关闭文件其实就是释放这个句柄的过程,使得进程与文件之间的连接断开。

2.系统对句柄的管理


用户通过 open() 来申请资源,在系统内核中实际会调用 sys_open() 方法来实现真正的资源申请工作。sys_open() 的代码在 fs/open.c 中:long sys_open(const char * filename, int flags, int mode) 。调用参数 filename 实际上是文件的路径名(绝对路径或者相对路径名);mode 表示打开的模式,如只读等等;flag 则包含了许多标志位,可以表示打开模式以外的一些属性和要求。

a)调用 getname() 从用户空间把文件的路径名拷贝到系统空间

b)调用 get_unused_fd() 从进程的可使用的文件描述符中找出一个没有被使用的文件描述符(确切的说是文件描述符数组的下标)。

c)调用 flip_open() 打开一个创建的文件,并返回一个文件描述符。

用户向系统申请文件资源是否能够成功,主要取决于 get_unused_fd 函数。如果函数的返回值为大于 0 的正整数,则 sys_open 能够获得系统文件资源;如果函数的返回值为-1,说明申请失败,该进程所申请的文件数已经超过系统设置的最大值

3.句柄泄露

造成句柄泄露的主要原因,是进程在调用系统文件之后,没有释放已经打开的文件句柄。在 Linux 系统中,进程与文件之间是通过“打开文件”操作建立连接,文件系统会返回文件句柄来唯一标识进程与文件的连接。每当一个进程执行完毕之后,Linux 系统会将与进程相关的文件句柄自动释放。但是,如果进程一直处于执行状态,文件的句柄只能通过“关闭文件”操作来自我释放。与 Windows 系统的设置不同,Linux 系统对进程可以调用的文件句柄数做了限制,在默认情况下,每个进程可以调用的最大句柄数为 1024 个。超过了这个数值,进程则无法获得新的句柄。因此,句柄的泄露将会对进程的功能失效造成极大的隐患。

a)Linux 中,单个进程能够打开的最大文件句柄数量是可以配置的,系统默认是 1024。当单个进程打开的文件句柄数量超过了系统定义的值,就会出现“Too many files open”的错误提示。用户可以通过以下命令查看系统定义的最大值:

ulimit -n

cat /proc/sys/fs/file-max 查看系统所有进程能打开的句柄

b)Linux 有硬性和软性设置两种,都可以通过 ulimit 来设置.设置 H(硬性),S(软性)的值,系统重启恢复默认值。

ulimit -HSn 2048

c)修改

/etc/sysctl.conf --> fs.file-max = 6553560 所有进程打开句柄数

/etc/security/limits.conf --> 加入一下配置

* soft nofile 65535 

* hard nofile 65535

检测句柄的方法

lsof -p pid 查看pid进程的句柄

lsof 输出各列信息的意义如下:

COMMAND:进程的名称 
PID:进程标识符 
USER:进程所有者 
FD:文件描述符,应用程序通过文件描述符识别该文件。如 cwd、txt 等 
TYPE:文件类型,如 DIR、REG 等 
DEVICE:指定磁盘的名称 
SIZE:文件的大小 
NODE:索引节点(文件在磁盘上的标识)

NAME:打开文件的确切名称

 或者使用

ls -la /proc/pid/fd/

检测文件占用内存大小

pmap pid

  • -x extended显示扩展格式
  • -d device显示设备格式
  • -q quiet不显示header/footer行
  • -V 显示版本信息

显示内容意义:

  • Address: 内存开始地址
  • Kbytes: 占用内存的字节数(KB)
  • RSS: 保留内存的字节数(KB)
  • Dirty: 脏页的字节数(包括共享和私有的)(KB)
  • Mode: 内存的权限:read、write、execute、shared、private (写时复制)
  • Mapping: 占用内存的文件、或[anon](分配的内存)、或[stack](堆栈)
  • Offset: 文件偏移
  • Device: 设备名 (major:minor)
mapped 表示该进程映射的虚拟地址空间大小,也就是该进程预先分配的虚拟内存大小,即ps出的vsz
writeable/private  表示进程所占用的私有地址空间大小,也就是该进程实际使用的内存大小      

shared 表示进程和其他进程共享的内存大小

Android中FD泄漏的几种类型

1. Resource related

Android应用可能会需要很多资源,像输入输出流,数据库资源Cursor, Binder设备。如果没能够很好的处理这些资源,不仅可能造成内存的泄漏,也可能会出现FD泄漏。

输入输出流

FileInputStream,FileOutputStream,FileReader,FileWriter 等输入输出如果不断创建但是不及时关闭,不仅可能造成内存的泄露了也可能会造成FD的溢出。在/proc/${进程id}/fd/ 目录下执行ls –l查看到增加的FD指向创建的文件,这里创建了不同的file,即使是对同一个文件,也会创建多个FD来指向这个打开的文件流。正确的做法是能够在final中将流进行关闭,这样无论中途是否出现异常导致程序中断,都会将流顺利关闭。

最终导致应用进程出现FC,并打出如下的Log, 表示这个进程FD数量已经到达了上限,无法再创建新的FD,只有终止进程。

E/Parcel ( 3601): dup failed in Parcel::read, fd 1 of 2
E/Parcel ( 3601): dup(1020) = -1 [errno: 24 (Too many open files)]
E/Parcel ( 3601): fcntl(1020, F_GETFD) = 1 [errno: 24 (Too many open files)]

E/Parcel ( 3601): flat 0x0 type 0

Cursor leak

与输入输出相似,数据库查询的Cursor如果没有及时进行Close,也会出现FD泄漏的情况。看到unable to open database file,大胆地猜测是否是出现了FD的泄漏。

AndroidRuntime: FATAL EXCEPTION: IntentService[ContactSaveService] 
AndroidRuntime: Process: com.android.contacts,
AndroidRuntime: android.database.sqlite.SQLiteException: unable to open database file (code 14) 
AndroidRuntime:     at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:179) 
AndroidRuntime:     at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135) 
AndroidRuntime:     at android.content.ContentProviderProxy.delete(ContentProviderNative.java:544) 
AndroidRuntime:     at android.content.ContentResolver.delete(ContentResolver.java:1330) 
AndroidRuntime:     at com.android.contacts.ContactSaveService.saveContact(ContactSaveService.java:478) 

AndroidRuntime:     at com.android.contacts.ContactSaveService.onHandleIntent(ContactSaveService.java:222) 

2.Thread related

HandlerThread在onCreate中创建,需要在onDestory中mHandlerThread.quitSafely();
对于传统的Java Thread,需要声明Loop以后才会出现FD的增加。因为声明Loop相当于增加了一块缓冲区,需要有一个FD来标识。 如果确定不需要Looper,可以使用Looper.quit()或者Looper.quitSafely()来退出looper,避免出现FD泄漏。

3.Input channel file realted

 onConfigurationChanged代码中出现了WindowManager.addView反复调用,却缺少对应的removeView匹配的情况。
而ActivityManagerProxy.getTaskThumbnail需要创建一个Parcel与ActivityManagerService通信,这时候就会创建一个FD指向Socket的端口,如果此时发现FD已经满了就会报出异常。
设置为Intent.FLAG_ACTIVITY_MULTIPLE_TASK类似flag的时候,如果没有处理好Activity的生命周期,可能会出现system_server进程先于应用进程到达FD上限,造成 Android系统重启。

问题的关键字:

too many files open,ashmem_create_region failed for ‘indirect ref table’: Too many open files,leak , release

快速定位问题:

ls -la /proc/pid/fd查看

a)anon_inode:[eventpoll]” 和 "pipe" :开启了太多的HandlerThread/Looper/MessageQueue, 线程忘记关闭, 或者looper 没有释放

b)大批量的socket 打开, 可能是因为Input Channel 没有关闭

c)大批量的打开“/dev/ashmem”, 如果是Context provider, 或者其他app, 很可能是打开数据库/数据库链接没有关闭


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值