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
    评论
Android OOM(Out of Memory)是一种常见的运行时异常,指的是应用程序内存不足的错误。当应用程序试图使用超过系统分配给它的内存时,就会出现这种异常。这可能是由于应用程序在后台加载大量数据、存储过多的对象或图像,或者由于系统资源管理器分配的内存不足所致。 为了解决Android OOM问题,您可以采取以下几种策略: 1. 优化您的代码以减少内存使用量:使用正确的数据类型,避免创建不必要的对象,限制图像和资源的数量,以及优化后台加载过程等。 2. 回收不再使用的内存:当您的应用程序不再需要使用某些内存时,应该及时回收它们。这可以通过调用垃圾回收器(Garbage Collector)来完成。 3. 避免在主线程上执行耗时操作:如果您的应用程序在主线程上执行耗时操作(如大量数据处理),这可能导致系统资源管理器超载,从而引发OOM异常。应该将这些操作移至后台线程。 4. 使用内存分析工具:内存分析工具可以帮助您识别内存泄漏和无效内存引用等问题,从而避免OOM异常的发生。 5. 配置您的应用程序以适应不同的内存配置:如果您正在开发一个需要大量内存的应用程序,您应该考虑在AndroidManifest.xml文件中配置您的应用程序以适应不同的内存配置。例如,您可以设置您的应用程序需要的最低和最高内存限制。 请注意,解决Android OOM问题是一个复杂的过程,需要您仔细分析和优化您的代码。如果您遇到了OOM问题,建议寻求专业的帮助或与开发社区进行讨论。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值