最近遇到一個應(yīng)用閃退的問題瘾带,開始發(fā)現(xiàn)問題時沒有找到明顯的問題復(fù)現(xiàn)步驟,單獨操作應(yīng)用里的所有功能都沒有發(fā)生該問題穿挨,而一旦進(jìn)行煲機(jī)測試半小時后出現(xiàn)了月弛。光從應(yīng)用層的log也分析不出問題的原因來,這個問題拖了很長一段時間科盛,真是慚愧!煲機(jī)的業(yè)務(wù)邏輯是通過PC程序循環(huán)發(fā)送指令到車機(jī)板子上菜皂,控制車機(jī)上的測試程序贞绵,調(diào)起測試case執(zhí)行硬件模塊的測試。
問題分析
分析該問題發(fā)生時的log文件恍飘,與我的應(yīng)用有關(guān)的log內(nèi)容主要是正常的業(yè)務(wù)打印榨崩,直到在crash log部分最后有一段包含了我的進(jìn)程名的內(nèi)容,這些log信息甚是可疑章母!
03-03 17:22:16.053 9320 9320 D TestCaseManager: enqueueCase: {mTitle:GPS, mID:GPS, mTestQueueType:OTHER}
03-03 17:22:16.053 9320 9320 D TestCase: secureStart() mStatus=PASS, mID=GPS
03-03 17:22:16.053 9320 9320 V TestCaseManager: onTestResult id:GPS, status:TESTING
03-03 17:22:16.053 9320 9320 D TestCase: secureStart()->invoke the start() of testcase GPS
03-03 17:22:16.055 9320 9320 W zygote64: ashmem_create_region failed for 'indirect ref table': Too many open files
03-03 17:22:16.056 9320 10862 F Looper : Could not make wake event fd: Too many open files
03-03 17:22:16.057 9320 9320 I GpsUtils: isProviderEnabled:true
03-03 17:22:16.057 598 676 D SocketMoudle: connect: bind failed, try
03-03 17:22:16.138 598 676 D SocketMoudle: connect: bind failed, try
03-03 17:22:16.205 888 961 W DCS_SocketMoudle: connect: accept() error, mCurSockFd=-1, errno=11
03-03 17:22:16.205 888 961 W CanDataProc: rxThread: socket connect retVal: -1
03-03 17:22:16.218 598 676 D SocketMoudle: connect: bind failed, try
03-03 17:22:16.275 10865 10865 I crash_dump64: obtaining output fd from tombstoned, type: kDebuggerdTombstone
03-03 17:22:16.276 936 936 I /system/bin/tombstoned: received crash request for pid 9320
03-03 17:22:16.277 10865 10865 I crash_dump64: performing dump of process 9320 (target tid = 10862)
03-03 17:22:16.277 10865 10865 I crash_dump64: engrave_tombstone
03-03 17:22:16.277 10865 10865 D DEBUG : enter engrave_tombstone
03-03 17:22:16.278 10865 10865 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
03-03 17:22:16.278 10865 10865 F DEBUG : Build fingerprint: 'M01_AE-userdebug-M01.CHJ.A81.M.200225.205'
03-03 17:22:16.278 10865 10865 F DEBUG : Revision: '0'
03-03 17:22:16.278 10865 10865 F DEBUG : ABI: 'arm64'
03-03 17:22:16.278 10865 10865 F DEBUG : pid: 9320, tid: 10862, name: Gps-Handler >>> com.xxx.car.vehicleeol <<<
03-03 17:22:16.278 10865 10865 F DEBUG : signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
03-03 17:22:16.295 10865 10865 F DEBUG : Abort message: 'Could not make wake event fd: Too many open files'
03-03 17:22:16.295 10865 10865 F DEBUG : x0 0000000000000000 x1 0000000000002a6e x2 0000000000000006 x3 0000000000000008
03-03 17:22:16.295 10865 10865 F DEBUG : x4 fefeff7c6fd54487 x5 fefeff7c6fd54487 x6 fefeff7c6fd54487 x7 7f7f7f7f7fff7fff
03-03 17:22:16.295 10865 10865 F DEBUG : x8 0000000000000083 x9 8d9b2e0bd9a2bbef x10 0000000000000000 x11 ffffffffffffffff
03-03 17:22:16.295 10865 10865 F DEBUG : x12 0000000000000018 x13 ffffffffffffffff x14 ffffffffff000000 x15 ffffffffffffffff
03-03 17:22:16.295 10865 10865 F DEBUG : x16 000000635aa7cfa8 x17 0000007e0d70b5d8 x18 000000000000001a x19 0000000000002468
03-03 17:22:16.295 10865 10865 F DEBUG : x20 0000000000002a6e x21 0000000000000083 x22 0000007d70d64588 x23 0000000013640100
03-03 17:22:16.295 10865 10865 F DEBUG : x24 0000000000000008 x25 0000007d70d64588 x26 0000007d5f2f6ea0 x27 0000000000000002
03-03 17:22:16.295 10865 10865 F DEBUG : x28 0000000000000002 x29 0000007d70d63090 x30 0000007e0d6bebbc
03-03 17:22:16.295 10865 10865 F DEBUG : sp 0000007d70d63050 pc 0000007e0d6bebdc pstate 0000000060000000
03-03 17:22:16.299 598 676 D SocketMoudle: connect: bind failed, try
03-03 17:22:16.379 598 676 D SocketMoudle: connect: bind failed, try
看到這樣的log母蛛,對于年幼無知的我,感到一臉蒙逼叭樵酢彩郊!在百度上谷歌一下Too many open files,這個問題涉及到Linux系統(tǒng)文件句柄超限蚪缀,是Linux進(jìn)程的fd泄露問題秫逝,這是什么鬼?得好好研究一下了询枚!
繼續(xù)分析上面問題發(fā)生的原因违帆,上面的log中打印 Could not make wake event fd: Too many open files這句log之前有一句打印是
可以發(fā)現(xiàn)是進(jìn)程com.xxx.car.vehicleeol里的線程Gps-Handler引起了fd泄露的問題,搜索代碼金蜀,在我的工程里發(fā)現(xiàn)了如下創(chuàng)建這個線程的方法:pid: 9320, tid: 10862, name: Gps-Handler >>> com.xxx.car.vehicleeol <<<
init()這個方法會在每次執(zhí)行GPS檢測項時調(diào)用刷后,GPS檢測結(jié)束后會將mLocateOn設(shè)置為false,所以每次執(zhí)行這個方法均會執(zhí)行創(chuàng)建HandlerThread的代碼渊抄,前文說的煲機(jī)測試就是一遍一遍地執(zhí)行這些測試項尝胆。上面問題發(fā)生的原因應(yīng)該就在這里了,應(yīng)用進(jìn)程在不斷地創(chuàng)建HandlerThread對象抒线,并執(zhí)行start()班巩。
為什么HandlerThread與fd泄露有關(guān)系?fd究竟在Linux OS中是什么?發(fā)生fd泄露的問題應(yīng)該如何解決呢抱慌?借著解決這個問題的機(jī)會逊桦,我?guī)е@些疑惑深入學(xué)習(xí)一下fd泄露的問題。
fd真相揭秘
Fd的全稱是File descriptor抑进,在Linux OS里强经,所有都可以抽象成文件,比如普通的文件寺渗、目錄匿情、塊設(shè)備、字符設(shè)備、socket、管道等等胁澳。當(dāng)通過一些系統(tǒng)調(diào)用(如open/socket等)踏拜,會返回一個fd(一個數(shù)字),然后根據(jù)這個fd對應(yīng)的文件進(jìn)行操作,比如讀、寫。在內(nèi)核進(jìn)程結(jié)構(gòu)體task_struct中為每個進(jìn)程維護(hù)了一個數(shù)組跷车,數(shù)組下標(biāo)就是fd,里面存儲的是對這個文件的描述橱野。
Linux默認(rèn)每個進(jìn)程能打開的fd數(shù)最大值是1024朽缴,當(dāng)應(yīng)用進(jìn)程打開的文件數(shù)超過這個限制值后就無法再打開文件了,系統(tǒng)會報相關(guān)的錯誤水援,Too many open files是Linux系統(tǒng)中程序打開的文件數(shù)過多的常見錯誤密强。
對于HandlerThread,我知道它實際上是一個Thread加上了一個Looper裹唆,在其run()方法中調(diào)用Looper.prepare()和Looper.loop()方法誓斥,這樣就可以在子線程中運行Handler的handlerMessage方法進(jìn)行消息處理了,這是之前對HandlerThread的淺顯的認(rèn)識许帐。繼續(xù)跟蹤源碼發(fā)現(xiàn)每一個Looper在創(chuàng)建的時候會打開兩個fd劳坑,一個是eventfd,另外一個是mEpolled成畦。上面問題中報的就是創(chuàng)建wake event fd時異常了距芬。
搜索網(wǎng)上對這個問題的分析和解決离斩,產(chǎn)生Too many open files問題主要有四種可能:
1银舱、單個進(jìn)程打開文件句柄數(shù)過多
2、操作系統(tǒng)打開的文件句柄數(shù)過多
3跛梗、systemd對該進(jìn)程進(jìn)行了限制
4寻馏、inotify達(dá)到上限
上面四種產(chǎn)生fd泄露的情況我歸納為兩方面的原因,一是系統(tǒng)本身限制了可操作的文件句柄數(shù)核偿,二是應(yīng)用的邏輯問題導(dǎo)致文件句柄超出了系統(tǒng)設(shè)定的限制诚欠。要解決fd泄露的問題,一方面可以通過修改系統(tǒng)參數(shù)漾岳,增大允許操作的文件句柄數(shù)轰绵;另一方面修改應(yīng)用程序內(nèi)部邏輯,使得程序在運行時不會超過系統(tǒng)允許的最大文件句柄數(shù)尼荆。
fd泄露其他案例
可能引起fd泄露問題的情況有多種左腔,fd泄露問題的主要特點是:
1、同一個問題可能出現(xiàn)在不同的堆棧
2耀找、fd發(fā)生泄露時可能不存在內(nèi)存不足的問題翔悠,觸發(fā)GC不一定能回收創(chuàng)建的文件句柄
fd泄露問題的關(guān)鍵日志:
"Too many open files"
"Could not allocate JNI Env"
"Could not allocate dup blob fd"
"Could not read input channel file descriptors from parcel"
"pthread_create"
"InputChannel is not initialized"
"Could not open input channel pair"
容易引起fd泄露的常見情況:
1、HandlerThread
如前文分析野芒,HandlerThread引發(fā)的fd泄露是由于頻繁地創(chuàng)建HandlerThread對象,內(nèi)部的Looper創(chuàng)建會導(dǎo)致fd超限双炕。
2狞悲、Thread.start()
調(diào)用Thread.start()啟動線程后,在native層調(diào)用open方法創(chuàng)建ashmem rigions妇斤,這會產(chǎn)生fd摇锋,如果循環(huán)地創(chuàng)建啟動過多的線程就會產(chǎn)生fd泄露。
3站超、Resource相關(guān)
使用輸入輸出流沒有關(guān)閉可能會出問題荸恕,F(xiàn)ileInputStream、FileOutputStream死相、FileReader融求、FileWriter 等每打開一個文件需要fd,一些輸入流也提供了基于fd的構(gòu)造方法算撮。文件流在finally塊中調(diào)close方法生宛,或者使用Java 7 編譯器和運行環(huán)境支持的 try-with-resources 語句可以避免fd泄露。
4肮柜、InputChannel
對于system server,陷舅,如果有大量的socket 打開,可能是因為Input Channel沒有關(guān)閉审洞,抓取hprof查看system server中WindowState的情況進(jìn)行分析莱睁。
5、Bitmap
創(chuàng)建Bitmap在native層會產(chǎn)生fd,如果使用完后不關(guān)閉仰剿,可能引發(fā)fd的泄露创淡。
Thanks To
Too many open files的四種解決辦法
too many open files(打開的文件過多)解決方法
Linux中Too many open files 問題分析和解決
應(yīng)用與系統(tǒng)穩(wěn)定性第三篇---FD泄露問題漫談