进程中android中io、socket、thread对于操作系统是什么?最多能创建多少个?
前言
去年工作中发现应用跑了很长一段时间后,突然:
- 数据库打不开
- sharedpreferences无法读取
- 网络无法连接
- 文件不可读写
总之就是一切涉及IO的操作都不能做。其中找了很多办法来试图解决这个问题,都是不痛不痒的无法定位到问题所在。如追溯日志、debug立即调试均无异常。
最终在使用多机型和不同的android版本上复现了。复现的步骤比较粗暴,就是把网络断掉。然后让程序跑一会儿。也是后面才发现可以这样复现,之前每次要跑十几个小时才会出一次这样的问题。还是非必现的。
一般的应用也许很难碰到这样的场景,我先讲一个前置条件。
- 频繁创建socket
- 频繁的IO读写
- 频繁的线程创建和关闭
先简单说一下这个问题的产生原因和解决办法,后面我们再从中分析原理。
应用具备了和服务端保持长链接的能力,保持实时push数据给服务器,由于我们做了重连机制,最后发现在弱网环境下由于消息无法送达socket会重复的断开和重连服务器。又因为使用socket是在java层写的close()
并非真实关闭释放,android虚拟机在4.4以后的版本在某些场景下就有可能无法成功释放linux层fd。导致每重连一次fd就会+1。最终会因为fd达到了linux默认值上限后而无法创建导致android层看不到异常也无法正常使用IO相关的api工作。
解决的办法就是优化重连机制,把socket下沉到native去维护。java不要在干这件事了。
fd是什么
fd(file descriptor):
直译就是文件描述符,它表示当前打开的文件索引。不管我们是创建socket、io、线程最终对应到linux底层都是文件描述符的形式,相信对linux有认识的同学都听说过一句话叫:“Linux下,一切皆文件”。在这个平台上其实不管我们做的什么操作,最终都对应成了文件。我们看一下维基百科怎么说的吧。
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
定位
如果你现在手上有一台root过的android设备,可以通过shell到/proc/{PID}/fd目录下使用查看当前应用的文件描述符数量。如下:1
2
3su
cd /proc/{PID}/fd
ls | wc -w
其中PID是进程id。去年验证该问题的时候我采用了观察断网前后来排查该错误,以下是当时的笔记部分:
- 断网前:95左右跳动(这里的跳动是正常close)
- 断网后:从95自增飙升到一千多随后开始IO不可用
1 | ls -l |
可以看到有pipe和socket,这里的pipe是Linux进程通信管道。暂时不去管它。socket的数目明显不对。断网重连网络,再到释放网络。这里的释放根本不起作用。虽然java调用了close方法。但是在fd里的描述符仍然被持续占用。到占满后就导致不允许再创建任何文件描述符。
带着这样的疑问,我又尝试在4.4和6.0的android版本中做测试,发现在android4.4中会释放。到android 6.0却不会释放。由于真设备是从4.4升级到了6.0在当初开发这个模块的时候问题并没有暴露出来。而换了6.0以后也需要再非常极端的网络不稳定和数个小时候才能复现。问题迟迟被拖了这么久没有被解决。
从用户反馈的角度来看就是设备(应用)死机了。表象上去看没有任何错误日志,实际是日志系统并没有记录下来,因为压根没法记录(IO不可用)。
- 1.本地log自动记录失效。
- 2.网络上报不可用。
fd描述符被占满了导致文件和网络都不可用,这就是为什么,线上的设备(应用)挂了,而去拿日志的时候什么都没有的原因。
fd最大值
我们可以通过/proc/pid/limits
,来查看当前进程允许使用的IO数目总量。如下:
1 | hx_s905x:/proc/4973 # cat limits |
这里的 Max open files 字段就是指明我们的fd描述符允许被创建的总数。
排查总结
- fd允许的最大值:
/proc/pid/limits
- 获取当前应用的pid:
ps
- 当前应用的状态:
/proc/pid/fd
- 数量:
ls | wc -w
我们只需要上面这些命令就可以了。检测的方式就是查看数目的抖动情况,如果维持比较平衡。不会出现不释放的情况则为正常。如果抖动情况很大,或者只增长不减少则存在fd泄漏。目前为止认知受限,还没弄明白为什么java层已经明确调用了释放但底层却没有释放的原因。不过我们可以在应用层控制频繁的创建和及时的检测,一般的App应用很少会遇见这种问题,因为用户用完就关了App。但是做平板、机顶盒、大屏触控(突然想起TNT工作站)等需要长时间运行的app。涉及到了频繁的网络创建和IO操作的就有可能会碰到。
上面的内容大部分是去年做的笔记,其本质是如果我们无法了解外围环境本身,就很有可能在当前的认知维度上做出错误的判断。假如当时我咬定是rom的错误,而不去学linux,基本上这个问题也就没法解决了。经常我会犯认知错误,就是站在自己所了解的情况,去认为一件事。结果往往还是错误的。