Android-Fk[Stability]: Android Fd洩漏問題分析
Android-Stability【Fdleak】: Android Fd洩漏問題分析
本文主要內容
1.Fd leak問題概述
2.需要open Fd的場景(易發或躺槍的地方)
3.Fd洩漏問提需要的log資訊有哪些
4. 怎麼獲取這些有用的資訊
本文序圖的uml檔案及簡化圖片的draw.io的xml檔案已分享至百度雲,如有修改可以下載自行修改:
https://pan.baidu.com/s/1I9GlkVmeSCKAS8JEqCbSaA
1. Fd leak問題概述
1.1 fd limit
如圖可以檢視某個進成的fd資訊:
系統對程序使用系統資源有相關限制cat /proc/pid/limits 即可看到:
soft Limit 軟限制 : 系統資源的使用的上限值
hard Limit 硬限制 :但不能超出hard limits值
如上看到系統對3207程序的的資源限制情況,其中對於FD資源限制為max open file一行,上限制是1024,即使用的fd如果已滿1024了,就會沒有fd資源提供,再需要fd的時候就會出現異常,即fd洩漏問題。
1.2 更改limit方法
程序可以通過如下方法獲取fd限制資訊,也可以通過setrlimit方法對本程序fd限制進行調整,但最大不能超過hardlimit限制:
a. Native 方法:
getrlimit(RLIMIT_NOFILE, &rlim);
setrlimit(RLIMIT_NOFILE, &rlim);
b. Java 方法:
android.system.Os.getrlimit(OsConstants.RLIMIT_NOFILE);
java方法未實現setrlimit方法,如果是手機廠商的話可以嘗試新增一個setrlimit方法的java介面。
c. 終端:
這裡要注意的是隻有security組的成員可使更改rlimit永久生效,普通使用者的更改的程序退出侯失效,重新進入該程序後將恢復使用預設的1024.
1.3 Fd洩漏問題
由上可以看出,fd資源不足時,再需要fd的執行緒可能就會產生異常,而需要fd資源的場景很多,即在fd資源不足時,這些需要fd資源的很多場景,程式碼都有可能出現異常從而程序crash或功能異常,因此這些場景可能只是躺槍導致程序FC,真正罪魁禍首應該是大量佔用fd資源的地方。
因此,Fd洩漏的原因一般都有如下特點:
1. 同一個問題可能出現不同堆疊
2. 比較隱晦
2. 需要open Fd的場景
同一個問題可能有不同的堆疊,但都一般都是需要fd時的堆疊,因此,如果遇到類似需要fd的堆疊都可以懷疑是否是發生了fd洩漏了,當然日誌中伴隨者大量“Too many open files”的字眼。
如下Java層的Error Msg均有fd洩漏的嫌疑:
“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”
…
當然還有許多其他型別的Error Msg,後續補充。
大致看下比較常見需要fd的場景:
2.1 Resource相關
使用BufferedReader去讀取檔案內容是常見的方式,bufferedReadernew出來需要及時關閉,注意如上br.close在try裡面,如過close之前發生異常未走到close則這裡便有fd洩漏的風險,因此br.close應當在finally塊中操作,保證一定能close掉。
詳細流程(雖然基於M的code來看,但基本如下最終呼叫系統函式open,會生成一個int的fd值):
簡化過程:
2.2 HandlerThread
HandlerThread是自帶looper的thread,使用時可以使用該looper構造handler來使用handler的功能,一個HandlerThread對應一個Looper成員變數
而Looper物件初始化時Looper.prepare() 需要fd資源,而且是一個HandlerThread起來會消耗一對fd(eventFd和epollFd),這兩個fd的目的也很明確,就是用來實現執行緒間通訊的。
詳細過程如下:
簡化過成如下:
如過是HandlerThread異常導致的fd洩漏,我們在該程序的Fd資訊中,下面是解問提時遇到的systemui的fd洩漏案例:
HandlerThread開啟過多時的fd資訊,ls -la proc/{pid}/fd/ 獲取:
fd 775: anon_inode:[eventfd]
fd 776: anon_inode:[eventpoll]
fd 777: anon_inode:[eventpoll]
fd 778: anon_inode:[eventfd]
fd 779: anon_inode:[eventfd]
fd 780: anon_inode:[eventpoll]
fd 781: anon_inode:[eventpoll]
fd 782: anon_inode:[eventpoll]
...
fd 808: anon_inode:[eventpoll]
fd 809: anon_inode:[eventfd]
fd 810: /dev/ashmem
fd 811: anon_inode:[eventpoll]
fd 812: anon_inode:[eventfd]
fd 813: anon_inode:dmabuf
fd 815: anon_inode:[eventfd]
fd 816: anon_inode:[eventpoll]
fd 817: anon_inode:sync_file
...
fd 832: anon_inode:[eventpoll]
fd 833: anon_inode:[eventfd]
fd 834: anon_inode:[eventpoll]
fd 835: anon_inode:[eventpoll]
fd 836: anon_inode:[eventfd]
fd 837: anon_inode:[eventpoll]
fd 838: /dev/ashmem
fd 839: anon_inode:[eventpoll]
fd 840: anon_inode:[eventfd]
fd 841: anon_inode:[eventfd]
...
fd 860: anon_inode:[eventpoll]
fd 861: anon_inode:[eventfd]
fd 862: anon_inode:[eventfd]
fd 863: anon_inode:[eventpoll]
fd 864: anon_inode:[eventfd]
fd 865: anon_inode:[eventfd]
fd 866: anon_inode:[eventpoll]
fd 867: anon_inode:[eventpoll]
fd 868: anon_inode:[eventfd]
fd 869: anon_inode:[eventpoll]
可以看到開啟的eventfd,eventpoll型別問提異常的多。
再看下該程序的程序trace或ps資訊中的執行緒情況:
pid: 11019, tid: 7441, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 7532, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 7534, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 7632, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 7806, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 7856, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8116, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8304, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8388, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8467, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8565, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8697, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8760, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8853, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 8940, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 9107, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 9215, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 9311, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 9331, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 9596, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 9930, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10036, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10069, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10127, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10231, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10331, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10449, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10568, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10674, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10772, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10822, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 10920, name: async_sensor >>> com.android.systemui <<<
pid: 11019, tid: 11113, name: async_sensor >>> com.android.systemui <<<
可以看到async_sensor這個執行緒異常的多,很明顯是該執行緒異常起的過多導致了fd洩漏,可以繼續排查該執行緒的異常起這麼多的原因,從而解決該fd洩漏問提。
2.3 Java Thread Start
Java在起執行緒的時候也會需要開Fd資源,如果執行緒生命週期操作不當,當起的執行緒過多時也會導致Fd洩漏的問提,當然這裡也是會經常躺槍。
可以先閱讀該篇文章進行詳細的學習:https://www.jianshu.com/p/e574f0ffdb42
詳細過成:
簡化過成:
對於1的地方,如果Fd資源不足了,會報出類似如下呼叫棧錯誤,應該是已經有Fd洩漏了導致再起執行緒時這裡建立JNIENV需要開啟Fd失敗,當然這裡有可能只是躺槍:
java.lang.OutOfMemoryError: Could not allocate JNI Env
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:729)
at com.android.server.wifi.WifiNative.startHal(WifiNative.java:1639)
at com.android.server.wifi.WifiStateMachine.setupDriverForSoftAp(WifiStateMachine.java:3970)
at com.android.server.wifi.WifiStateMachine.-wrap9(WifiStateMachine.java)
at com.android.server.wifi.WifiStateMachine$InitialState.processMessage(WifiStateMachine.java:4480)
at com.android.internal.util.StateMachine$SmHandler.processMsg(StateMachine.java:980)
at com.android.internal.util.StateMachine$SmHandler.handleMessage(StateMachine.java:799)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:163)
at android.os.HandlerThread.run(HandlerThread.java:61)
對於2的地方,如果記憶體資源不足了,或者沒有連續虛擬地址可用會丟擲如下類似錯誤呼叫棧,這個時候應該去調查為何虛擬地址不足,這也是OOM的問提了:
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:733)
at com.tencent.mm.sdk.f.b$a.start(SourceFile:61)
at com.tencent.mm.am.a.bU(SourceFile:60)
at com.tencent.mm.ui.MMAppMgr$8.tC(SourceFile:315)
at com.tencent.mm.sdk.platformtools.am.handleMessage(SourceFile:69)
at com.tencent.mm.sdk.platformtools.aj.handleMessage(SourceFile:173)
at com.tencent.mm.sdk.platformtools.aj.dispatchMessage(SourceFile:128)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:6701)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:246)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
復現該類問提之需要在fd不足時不斷起java執行緒,或記憶體緊張時起執行緒就比較容易復現類似的呼叫棧。
所以該類呼叫棧容易是躺槍,fd leak問提需要調查誰消耗的fd過多,oom問提需要找到是誰導致記憶體不足。
2.4 InputChannel
fd leak中一種比較常見的錯誤呼叫桟:
log1:
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: FATAL EXCEPTION: main
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: Process: com.miui.weather2, PID: 20556
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: java.lang.RuntimeException: Could not read input channel file descriptors from parcel.
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at android.view.InputChannel.nativeReadFromParcel(Native Method)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at android.view.InputChannel.readFromParcel(InputChannel.java:148)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at android.view.InputChannel$1.createFromParcel(InputChannel.java:39)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at android.view.InputChannel$1.createFromParcel(InputChannel.java:37)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at com.android.internal.view.InputBindResult.<init>(InputBindResult.java:68)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at com.android.internal.view.InputBindResult$1.createFromParcel(InputBindResult.java:112)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at com.android.internal.view.InputBindResult$1.createFromParcel(InputBindResult.java:110)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at com.android.internal.view.IInputMethodManager$Stub$Proxy.startInputOrWindowGainedFocus(IInputMethodManager.java:723)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at android.view.inputmethod.InputMethodManager.startInputInner(InputMethodManager.java:1295)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at android.view.inputmethod.InputMethodManager.onPostWindowFocus(InputMethodManager.java:1543)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:4069)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at android.os.Looper.loop(Looper.java:171)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6642)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:518)
06-22 20:34:43.035 10037 20556 20556 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
log2:
07-22 11:06:05.644 1526 1645 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: android.ui
07-22 11:06:05.644 1526 1645 E AndroidRuntime: java.lang.RuntimeException: Could not open input channel pair. status=-24
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at android.view.InputChannel.nativeOpenInputChannelPair(Native Method)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at android.view.InputChannel.openInputChannelPair(InputChannel.java:94)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at com.android.server.wm.WindowState.openInputChannel(WindowState.java:2011)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at com.android.server.wm.WindowManagerService.addWindow(WindowManagerService.java:1406)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at com.android.server.wm.Session.addToDisplay(Session.java:197)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at android.view.ViewRootImpl.setView(ViewRootImpl.java:750)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:356)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at android.app.Dialog.show(Dialog.java:330)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at com.android.server.am.AppErrors.handleShowAppErrorUi(AppErrors.java:755)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at com.android.server.am.ActivityManagerService$UiHandler.handleMessage(ActivityManagerService.java:1849)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:105)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at android.os.Looper.loop(Looper.java:171)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at android.os.HandlerThread.run(HandlerThread.java:65)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at com.android.server.ServiceThread.run(ServiceThread.java:46)
07-22 11:06:05.644 1526 1645 E AndroidRuntime: at com.android.server.UiThread.run(UiThread.java:42)
當然類似inputchannel的異常呼叫棧不止如上兩個,還有
這裡inputchannel也是需要fd資源,AddWindow的時候需要初始化inputchannel去和InputManagerService進行跨程序通訊來監控Input事件,本質上是初始化了一對socket檔案進行通訊:
詳細過程:
可以通過該部落格進行學習InpuntChannnel相關的知識:https://blog.csdn.net/hongzg1982/article/details/54812359
簡化過程:
如上知道addWindow或創建出一對socket檔案用於構造inputchannel,從而實現app從InputDispatcher那裡得到input事件,因此,如果此時Fd資源緊張,則很容易在addWindow時發生異常。
不過如過異常log顯示inputchannel相關的居多的話也有必要此時window存在過多,或異常新增window為銷燬導致socket增加而fd洩漏。
因此對於該類的異常,可以看看當前的window資訊,adb shell dumpsys window或得,即可以比較明確的看出是哪個window有異常,然後找原因解決。
類似的寫個該型別demo試下:
寫一個demo,點選按鈕讓其不斷的彈AlertDialog:
結果出現異常:
10-28 20:49:07.547 14246 14246 E AndroidRuntime: FATAL EXCEPTION: main
10-28 20:49:07.547 14246 14246 E AndroidRuntime: Process: com.example.chengang.fdtest, PID: 14246
10-28 20:49:07.547 14246 14246 E AndroidRuntime: java.lang.RuntimeException: Could not read input channel file descriptors from parcel.
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.InputChannel.nativeReadFromParcel(Native Method)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.InputChannel.readFromParcel(InputChannel.java:148)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.IWindowSession$Stub$Proxy.addToDisplay(IWindowSession.java:759)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.ViewRootImpl.setView(ViewRootImpl.java:669)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:319)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.app.Dialog.show(Dialog.java:325)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.app.AlertDialog$Builder.show(AlertDialog.java:1112)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at com.example.chengang.fdtest.MainActivity.showNormalDialog(MainActivity.java:70)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at com.example.chengang.fdtest.MainActivity.access$000(MainActivity.java:13)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at com.example.chengang.fdtest.MainActivity$1.onClick(MainActivity.java:31)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.View.performClick(View.java:5275)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.view.View$PerformClick.run(View.java:21559)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:815)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:104)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.os.Looper.loop(Looper.java:207)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:5845)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
10-28 20:49:07.547 14246 14246 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:768)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: android.ui
10-28 20:49:07.849 9259 9286 E AndroidRuntime: java.lang.RuntimeException: Failed to initialize display event receiver. status=-2147483648
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.DisplayEventReceiver.nativeInit(Native Method)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.DisplayEventReceiver.<init>(DisplayEventReceiver.java:71)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.Choreographer$FrameDisplayEventReceiver.<init>(Choreographer.java:824)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.Choreographer.<init>(Choreographer.java:219)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.Choreographer.<init>(Choreographer.java:79)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.Choreographer$1.initialValue(Choreographer.java:116)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.Choreographer$1.initialValue(Choreographer.java:109)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at java.lang.ThreadLocal$Values.getAfterMiss(ThreadLocal.java:430)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at java.lang.ThreadLocal.get(ThreadLocal.java:65)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.Choreographer.getInstance(Choreographer.java:249)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.ViewRootImpl.<init>(ViewRootImpl.java:501)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:306)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.app.Dialog.show(Dialog.java:325)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at com.android.server.am.ActivityManagerService$UiHandler.handleMessage(ActivityManagerService.java:1694)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:111)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.os.Looper.loop(Looper.java:207)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at android.os.HandlerThread.run(HandlerThread.java:61)
10-28 20:49:07.849 9259 9286 E AndroidRuntime: at com.android.server.ServiceThread.run(ServiceThread.java:46)
看到不僅demo app crash了,而且system_server也出現了異常crash,手機重啟了,可怕!!!
足見fd洩漏問題的嚴重性,也瞭解到app異常也會影響到system_server的穩定性。
為了不讓系統重啟,簡單的看log,下面日日誌是隻彈50個dialog時抓取到的資訊(其實也可以複寫UnCatchExceptionHandler,在異常退出前抓取所需的fd,hprof,dumpsys window資訊):
看下該異常時的fd資訊:
socket 檔案型別變多:
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 100 -> socket:[275165]
...
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 112 -> socket:[275177]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 113 -> socket:[275179]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 114 -> socket:[275181]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 115 -> socket:[275183]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 13 -> socket:[268102]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 20 -> socket:[244415]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 38 -> socket:[264306]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 4 -> socket:[159952]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 44 -> socket:[258994]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 46 -> socket:[262529]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 49 -> socket:[249721]
...
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 86 -> socket:[275153]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 87 -> socket:[275155]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 88 -> socket:[275157]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 89 -> socket:[270815]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 90 -> socket:[266061]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 91 -> socket:[270817]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 92 -> socket:[275159]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 93 -> socket:[275161]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 94 -> socket:[266063]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 95 -> socket:[270819]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 96 -> socket:[272449]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 97 -> socket:[266065]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 98 -> socket:[272451]
lrwx------ u0_a158 u0_a158 2018-10-28 21:17 99 -> socket...
ashmem型別也變多,可能跟surface建立surfaceflinger需要通過ashmem型別檔案傳遞顯示資料導致(暫未證實,後續調查)
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 520 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 521 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 522 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 523 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 525 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 526 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 527 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 528 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 529 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 53 -> /dev/ion
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 530 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 531 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 532 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 533 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 534 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 535 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 536 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 537 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 538 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 539 -> anon_inode:dmabuf
lr-x------ u0_a158 u0_a158 2018-10-28 21:04 54 -> /proc/ged
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 540 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 541 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 542 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 543 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 544 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 545 -> anon_inode:dmabuf
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 546 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 547 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 548 -> /dev/ashmem
lrwx------ u0_a158 u0_a158 2018-10-28 21:04 549 -> /dev/ashmem
可明顯看到socket型別的檔案變多,正是inputchannel增多導致,另外值得注意的是ashmem,dmabuf也明顯增多(後續調查原因);
看下該case的hprof對比:
彈dialog之前:
彈50個dialog之後:
明顯看出inputchannel增多。
看下該case下window的情況:
dumpsys window:
WINDOW MANAGER TOKENS (dumpsys window tokens)
WINDOW MANAGER WINDOWS (dumpsys window windows)
Window #56 Window{db0f6a6 u0 AOD}:
Window #55 Window{a450d9c u0 StatusBar}:
Window #54 Window{5261571 u0 KeyguardScrim}:
Window #53 Window{7eadf3a u0 AssistPreviewPanel}:
Window #52 Window{b58497e u0 DockedStackDivider}:
Window #51 Window{3aab4ea u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #50 Window{de9258c u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #49 Window{bce8de u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #48 Window{5843360 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #47 Window{7a80592 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #46 Window{6744bf4 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #45 Window{feaff06 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #44 Window{f5a4348 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #43 Window{b3d893a u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #42 Window{b1ad5c u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #41 Window{f84182e u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #40 Window{a4de30 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #39 Window{983dfe2 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #38 Window{9a0e9c4 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #37 Window{c56d456 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #36 Window{7a9a418 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #35 Window{19fa98a u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #34 Window{86da12c u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #33 Window{e7dd37e u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #32 Window{21a3500 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #31 Window{1418632 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #30 Window{4ef7394 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #29 Window{bdfb5a6 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #28 Window{b9430e8 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #27 Window{f2615da u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #26 Window{e2a00fc u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #25 Window{aaf1ace u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #24 Window{c2137d0 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #23 Window{595f882 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #22 Window{cce964 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #21 Window{3eaa2f6 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #20 Window{6b6e9b8 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #19 Window{ce5ce2a u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #18 Window{db3cccc u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #17 Window{5dcee1e u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #16 Window{7b6e6a0 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #15 Window{5f636d2 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #14 Window{664b34 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #13 Window{69c9c46 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #12 Window{36ece88 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #11 Window{4b3d27a u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #10 Window{598049c u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #9 Window{68c4d6e u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #8 Window{4984170 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #7 Window{a974122 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #6 Window{1a89904 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #5 Window{2daa196 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #4 Window{ad8df58 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #3 Window{12522ca u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #2 Window{ff6eac7 u0 com.example.chengang.fdtest/com.example.chengang.fdtest.MainActivity}:
Window #1 Window{2624127 u0 com.android.systemui/com.android.systemui.recents.RecentsActivity}:
Window #0 Window{df85d5b u0 com.android.systemui.ImageWallpaper}:
這一看window資訊就很明顯了com.example.chengang.fdtest.MainActivity有異常addWindow,
如此就可以確定異常code深入分析了。
2.5 Sqlite Cursor
類似的案例在該部落格中記錄的比較詳細:https://blog.csdn.net/jk198310/article/details/43765263
意思是遇到
"E CursorWindow: Could not allocate CursorWindow "
"Can’t open DB due to too many open files "
等錯誤Msg時可以懷疑可能有Fd 洩漏,但肯定不全是。
這裡只看下cursor查詢資料時也是需要開啟fd的,值得注意的是CursorWindow預設大小是2MB,如過使用不當可能不僅導致fd洩漏風險,還有記憶體洩漏風險。
通過Uri查詢資料庫所得到的資料集,儲存在native層的CursorWindow中。CursorWindow的實質是共享記憶體的抽象,以實現跨程序資料共享。共享記憶體所採用的實現方式是檔案對映。
在ContentProvider端透過SQLiteDatabase的封裝查詢到的資料集儲存在CursorWindow所指向的共享記憶體中。然後通過Binder把這片共享記憶體傳遞到ContentResolver端,即查詢端。
這樣客戶就能夠通過Cursor來訪問這塊共享記憶體中的資料集了。
詳細開啟fd過程:
2.6 Bitmap IPC
BitMap在IPC傳遞時需要開啟fd進行傳遞,比如如下情況:
一個應用使用包含一個bitmap的RemoteView去更新通知欄通知,更新一個通知實際上涉及到3個程序,app將通知IPC給system_server,system_server將通知轉給註冊收通知的程序,比如systemui 通知欄。
而如果通知中還有Bitmap,bitmap在進行IPC時實際上是通過傳遞指向bitmap所在的匿名共享記憶體的fd進行傳遞的,即大致如下:
詳細開啟fd的過程:
IPC時傳遞該Fd即可而不是傳遞整個Bitmap物件。
即大致如下:
a. app 中得bitmap存到ashmem中,IPC給system_Server時將指向該ashmem檔案的fd writetoparcel成parcel物件傳遞給system_server
b. system_server得到parcel物件再createFromParcel由該parcel中的fd得到bitmap所在的ashmem,即system_server中已IPC得到了app 傳來的bitmap物件。
c. system_server將bitmap傳地給註冊監聽的程序比如systemui通知欄使用同樣的方式,開闢ashmem儲存檔案儲存bitmap,使用封裝指向該ashmem的fd的parcel物件傳地給有關程序。
d. systemui同樣使用createFromParcel獲取到傳遞過來的fd從而等到該fd指向的ashmem,最後構造出最終的bitmap物件。
由以上可知,如過app斷更新帶bitmap的通知太過頻繁,可能會不僅導致本程序fd洩漏,還有可能影響到其他程序,甚至影響到system_Server導致重啟,畢竟system_Server作為中轉需要更多的fd資源。
類似case,log:
10-17 12:13:02.007 2096 2096 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: main
10-17 12:13:02.007 2096 2096 E AndroidRuntime: java.lang.RuntimeException: Could not copy bitmap to parcel blob.
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.graphics.Bitmap.nativeWriteToParcel(Native Method)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.graphics.Bitmap.writeToParcel(Bitmap.java:1553)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.widget.RemoteViews$BitmapCache.writeBitmapsToParcel(RemoteViews.java:984)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.widget.RemoteViews.writeToParcel(RemoteViews.java:2854)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.widget.RemoteViews.clone(RemoteViews.java:1903)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.app.Notification.cloneInto(Notification.java:1521)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.app.Notification.clone(Notification.java:1495)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.service.notification.StatusBarNotification.clone(StatusBarNotification.java:161)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.notification.NotificationManagerService$NotificationListeners.notifyPostedLocked(NotificationManagerService.java:3398)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.notification.NotificationManagerService$8.run(NotificationManagerService.java:2228)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:742)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:95)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.os.Looper.loop(Looper.java:157)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.SystemServer.run(SystemServer.java:302)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.SystemServer.main(SystemServer.java:176)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:746)
10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)
看下該case的fd資訊:
09-11 14:13:34.144 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/396 -----> /dev/ashmem
09-11 14:13:34.144 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/397 -----> /dev/ashmem
09-11 14:13:34.144 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/398 -----> /dev/ashmem
09-11 14:13:34.144 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/399 -----> /dev/ashmem
...
09-11 14:13:34.145 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/412 -----> /dev/ashmem
09-11 14:13:34.146 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/413 -----> /dev/ashmem
09-11 14:13:34.146 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/414 -----> socket:[9368579]
09-11 14:13:34.146 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/415 -----> /dev/ashmem
...
09-11 14:13:34.157 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/524 -----> /dev/ashmem
09-11 14:13:34.157 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/526 -----> /dev/ashmem
09-11 14:13:34.157 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/527 -----> /dev/ashmem
09-11 14:13:34.158 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/529 -----> /dev/ashmem
...
09-11 14:13:34.161 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/552 -----> /dev/ashmem
09-11 14:13:34.161 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/553 -----> /dev/ashmem
09-11 14:13:34.161 2546 2671 W FdInfoManager: MIUI_FD /proc/self/fd/555 -----> /dev/ashmem
fd資訊中ashmem資訊明顯很多,單通常只根據該異常呼叫棧,和ashmem較多的fd資訊還不足以斷定就是包含bitmap的通知彈的過多導致,這時需要檢視logcat,需要看下此時通知是否真得事很多,比如該case的logcat中搜notification相關的日誌出現大量更新notification的資訊,且很多和notification相關的異常,即可以朝這個方向去找是誰發的notification有大量的bitmap傳遞。
一般這樣的app如閱讀類app,計步類app會頻繁更新通知欄的應用嫌疑比較大。
關於bitmap的remoteview更新notification的問題事實上是aosp的原生bug,具體不在此討論。
當然,不僅bitmap,類似的需要使用到fd進行IPC傳遞的物件都有可能異常傳地而導致fd洩漏問題,或躺槍,==。
3. Fd洩漏問題分析需要什麼資訊
3.1 logcat 檢視異常棧情況,什麼資訊在頻繁的列印,涉及到fd open的日誌頻繁列印是最直接懷疑的地方。
3.2 fdinfo 檢視程序fd的開啟情況,ls -la /proc/{pid}/fd/ 即可,或者 lsof命令列印指定程序或所有程序的fd資訊,根據fd的開啟型別推測是什麼型別檔案的洩漏,多了fd型別線索然後根據日誌去推測。
3.3 trace or ps資訊 跟執行緒相關的fd洩漏通過程序的資訊可以很快定位出異常執行緒。
3.4 dumpsys window 通過window資訊可以很快定位出異常window用於解決inputchannel相關的fd洩漏問提。
4. Fd洩漏問題資訊獲取
當然知道怎麼分析fd洩漏問題還是遠遠不夠的,對於fd洩漏的問提最重要的應該就是相關資訊的日誌獲取了,沒有米怎麼做飯,沒有日誌怎麼分析,因此下面討論下關於fd洩漏相關的日誌獲取。
一. 容易復現,fd洩漏時可能會伴隨卡頓異常等現象,可以在程序復現未掛的時候抓取想要的資訊:
1.檢視fd資訊adb shell ls -a -l /proc//fd ,lsof
2.檢視程序執行緒資訊:ps -t ,或者抓程序trace, kill -3
3.抓取hprof定位資源使用情況
二.難復現,通常fd洩漏都不會是那麼容易復現的,那就需要特殊的日誌資訊獲取方式了:
1.對於應用自身fd洩漏發生JE時可以在複寫UncatchHandlerException在應用crash的時候通過readlink的方式讀取/proc/self/fd的資訊,以獲取fd資訊,抓取程序的ps資訊或者trace資訊,抓取window情況,dumpsys window等;
2.O之後NE的Tombstone檔案中有open files,可以檢視開啟的fd資訊;
3.針對rom廠商,可以在RuntimeInit.java中LoggingHandler 中對於Error Msg進行判斷,如懷疑有fd洩漏即讀取/proc/self/fd/中的fd資訊,記得使用readlink方法,當然當fd滿了的時候再讀的時候可能會讀不成功,嘗試使用i前面提供的方法提高程序的fd rlimit再讀即可(我廠手機就是這麼幹的):
這種方法可以針對所有java程序的fd洩漏異常時的fd資訊獲取,RuntimeInit.java跑在各個應用的程序中;
如過再做個daemon程序進行判斷是否需要抓取fd資訊則可以根據情況進行列印,比如在跑穩定性測試時預設列印所有fd洩漏的程序的fd資訊,方便debug分析。
4.如過自動化測試可以復現,也可以嘗試寫個指令碼,一定時間獲取下指定程序的fd資訊,通過lsof,或ls -la /proc//fd/方式獲取到fd數量,當fd數量高於指定值比如900後執行抓取需要的log資訊指令碼,比如logcat,trace,hprof,dumpsys window等;
5. 總結
總之就是根據開啟的檔案型別,以及上下文的log來推斷重複開啟為關閉的檔案控制代碼洩漏的程式碼位置。