android fd泄露分析,Android-FD泄露导致的OOM问题

最近遇到一个线上问题,因为业务需求需要文件下载功能,因此接入了Github上一个比较流行的下载库FileDownloader。

但是在线上却出现了oom的问题

[error:java.io.IOException: Cannot run program "logcat": error=24, Too many open files]

FileDownloader-Network44(702)

java.lang.OutOfMemoryError

Could not allocate JNI Env

java.lang.Thread.nativeCreate(Native Method)

java.lang.Thread.start(Thread.java:1063)

java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:921)

4 java.util.concurrent.ThreadPoolExecutor.processWorkerExit(ThreadPoolExecutor.java:989)

5 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1131)

6 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)

7 java.lang.Thread.run(Thread.java:818)

一般创建线程的时候出现OOM的问题,可以有以下的情况:

1)内存堆栈满了,没有多余的内存空间

2)进程创建的线程数量超过了进程的最大限制数量

3)进程的文件操作符数量超过了进程的最大限制数量

从崩溃的日志上看,这个问题是由于进程文件操作符超过了进程的最大限制造成的。

文件描述符

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

我们可以通过命令行来查看指定进程的文件描述符数量限制

首先切换到adb的shell,然后再获取指定的PID

$ adb shell //切换到shell环境

$ su //切换到超级管理员身份

# ps| grep "xxx.xxx.xxx"

//查找我们想要查看的进程的PID

u0_a211 15429 100 902 1270960 62088 SyS_epoll_ 00f24fff30 S xxx.xxx.xxx

cat /proc/15429/limits

Limit Soft Limit Hard Limit Units

Max cpu time unlimited unlimited seconds

Max file size unlimited unlimited bytes

Max data size unlimited unlimited bytes

Max stack size 8388608 unlimited bytes

Max core file size 0 unlimited bytes

Max resident set unlimited unlimited bytes

Max processes 13419 13419 processes

Max open files 1024 4096 files

Max locked memory 67108864 67108864 bytes

Max address space unlimited unlimited bytes

Max file locks unlimited unlimited locks

Max pending signals 13419 13419 signals

Max msgqueue size 819200 819200 bytes

Max nice priority 40 40

Max realtime priority 0 0

Max realtime timeout unlimited unlimited us

我们看到进程内的文件描述符数量是有限制的,如果超过了这个数量,就会出现OOM问题。

验证问题

我们在应用的基础上,调用文件下载库,进行串行下载实验。

切换到FileDownloader进程的FD目录下:

cd /proc/15429/fd

然后在查看当前FD目录下文件描述符数量:

ls -l | wc -l

94

可以看到目前该进程下是有94个文件描述符.

现在我们开始发起下载任务去下载一个MP3音频文件,然后在下载完毕之后再次查看FD目录.

ls -l | wc -l

99

可以看到,下载完之后,文件描述符并没有被释放,说明这时候出现了FD泄露。

那么,是什么操作导致文件描述符泄露,我们可以先查看FD目录下,哪些文件描述符没有被释放。

ls -l

lrwx------ 1 u0_a211 u0_a211 64 2018-04-20 17:36 0 ->/dev/null

lrwx------ 1 u0_a211 u0_a211 64 2018-04-20 17:36 1 ->/dev/null

lr-x------ 1 u0_a211 u0_a211 64 2018-04-20 17:36 10 ->/system/framework/core-oj

.jar

...//此处省略若干个文件描述符

lrwx------ 1 u0_a211 u0_a211 64 2018-04-20 17:36 100 ->/storage/emulated/0/GGMusic/Myudio/15241341_AAdAFrYcQGID2I0AABOcDTGpbMAAFvOAP_sUwAAE60322.mp3

可以看到音频文件相关的文件描述符没有被释放,可以推测,是在写入文件的时候,没有对相关的文件流进行关闭回收操作。

我们看到FileDownloader的源码:

FileDownloadRandomAccessFile.java

FileDownloadRandomAccessFile(File file) throws IOException {

randomAccess = new RandomAccessFile(file, "rw");

fd = randomAccess.getFD();

out = new BufferedOutputStream(new FileOutputStream(randomAccess.getFD()));

}

FileDownloadRandomAccessFile封装了FDw文件类以及文件写入流,在构造方法体内对这些变量进行初始化。

@Override

public void close() throws IOException {

out.close();

}

但是在FileDownloadAccessFile#close()方法内,却只对了BufferedOutputStream调用close回收操作,而没有对RandomAccessFile调用close方法对回收操作。这里造成了Fd文件描述符的泄露。

为什么出现的概率低

这个问题我已经给作者提交了PR,普通测试的情况下,这个case也是很难重现的。

1)一般场景不会触发那么多的下载需求,因为文件描述符的限制是1024,而每次进程重启,FD目录都会清空。

2)另一个原因是当一段时间不使用之后,FD资源在java的gc系统中会被释放掉。

我们可以看到java在IO流中的代码:

/** * 该段代码摘自FileInputStream源码,jdk版本1.8 */

protected void finalize() throws IOException {

if ((fd != null) && (fd != FileDescriptor.in)) {

/* if fd is shared, the references in FileDescriptor * will ensure that finalizer is only called when * safe to do so. All references using the fd have * become unreachable. We can call close() */

close();

}

}

因此,综上,出现开篇提到的oom问题,是用户在短时间内大量触发有FD泄露的线程导致的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值