Android System ANR caused SWT restart issue,androidanr
一、問題現象
1、使用者直觀看到的現象是System先ANR。
2、ANR之後系統重啟。
測試方法:
在錄音的介面不停的滑動音量進度條,同時有線電話給測試機打電話,電話沒有接通,只見介面凍結,彈出ANR,接著系統重啟。
Platform:MT6732
Android版本:4.4.4KK
BuildType:user
系統軟體版本:SWA3A+UMA0
系統RAM:1GB
問題機率:≈2%
參考機行為:
1、低機率問題,暫無參考機行為。
二、解決方案
通過初步分析、深入分析(具體分析過程、關鍵代碼和log在下面會附上)我們清楚的知道了問題發生的原因:
1、SystemServer進程中的AudioService相關的setindex和getindex等方法會在多個Thread中進行,而這些方法都是以synchronized關鍵字聲明的,即這些方法會以類的每個執行個體中的lock進行多線程同步。
2、這些synchronized的方法中存在穿插調用其他同類型執行個體的synchronized方法的行為。
3、完全將方法用synchronized關鍵字聲明是一個比較懶的做法,這樣會導致同步鎖的粒度太大,沒有細化臨界區,在多線程高並發的狀態下會降低代碼流執行的輸送量,而且會加大在穿插調用過程中彼此依賴發生死結碰撞的可能。
在當前代碼的執行狀態下就有一定機率(很小,依賴於具體的進程調度時機)出現因為調度原因而先調度到了Thread1並執行了對象A的同步方法,然後調度到了Thread2並執行對象B的同步方法,在對象B的同步方法中想要去調用對象A的同步方法,此時發生阻塞,接著又調度到了Thread1,繼續執行對象A的同步方法中的代碼,然後又去調用對象B的同步方法,因為在Thread2中已經持有了對象B的lock,所以此時Thread1也發生了阻塞,當前的狀態就是Thread1和Thread2相互等待對方釋放鎖而無限期等待,各種的代碼流得不到執行而死結。
考慮AudioService中複雜的邏輯,所以要以最小風險的改動來修複這個問題,因此這裡給出的方案沒有進行太大的改動,而且比較顯而易見的是全部加synchronized關鍵字的這些出問題的代碼,AOSP還有一定的最佳化空間。
最終針對以上問題的根本原因,我們給出以下解決方案:
1、更換同步鎖的類型
在需要同步的臨界代碼區使用類的全域鎖來代替每個執行個體自己的鎖,從而保證多個Thread在相互穿插調用時不會發生死結。
2、方案相關的具體代碼和backtrace
以上是發生死結時鎖對應的backtrace調用棧以及相應的代碼,通過紅線圈住部分我們可以看到發生問題時的關鍵調用關係和狀態。
3、最終方案的代碼修改
三、問題初步分析
以ALTO4.5TMO出問題時候的一份典型backtrace和log為例,發現出問題時SystemServer的主線程block在了一個AudioService內部的一個函數上,從而引發ANR和SWT重啟,具體backtrace如下:
為什麼會block?通過查看如上對應代碼,發現這個方法是個synchronized的,而且方法中在滿足條件時還會遍曆並調用同類型但是不同執行個體對象的synchronized方法,因此這裡被block就需要滿足一個條件:調用的同類型不同執行個體對象的synchronized方法無法進入,即在其他thread中已經進入了這個synchronized方法。
根據這個線索繼續查看SystemServer中與AudioService相關的thread的調用棧,找到Binder_2這個thread,具體的backtrace如下:
通過backtrace和對應代碼我們發現Binder_2這個thread也block在了一個AudioService內部的synchronized函數中,同樣的這個函數中在滿足一定的條件時也會調用同類型不同執行個體的synchronized方法。
四、深入分析問題
經過初步分析我們定位到了第一個問題點,即兩個不同的Thread都block在了同一個類型的synchronized方法上,同時也產生了1個問題,接下來我們繼續深入分析以期能到找到答案和問題的根本原因。
1、兩個Thread為什麼會同時block?
通過進一步分析和查看代碼發現,由於兩個Thread所執行的都是synchronized方法,如果它們由於調度和執行原因而產生了相互依賴的關係,那麼就會發生同時block的現象而死結,由於backtrace只能看到調用關係,不能知道運行時各個對象執行個體的狀態,所以我們根據backtrace類比systemserver中當前這兩個thread的問題狀態,結果完全符合當前的問題現象,具體的類比代碼如下:
先自訂一個Thread類,接收兩個TestSync類的執行個體並在run裡面調用執行個體1的同步方法,同時將執行個體2傳遞過去。
接著定義一個TestSync類,並定義兩個synchronized的成員函數,然後在每個函數開始的地方都先sleep 10ms,以滿足進程調度切換的狀態。
最後在activity的onResume方法中進行測試,結果測試的activity就會ANR,為什麼會ANR?
原理和上面systemServer ANR並SWT重啟一樣,這裡activity的UI主線程和建立的ct1線程發生了死結。
以上代碼的執行流程大致如下:
1、建立t1,t2兩個TestSync類的執行個體以及CThread類的執行個體ct1並將t1和t2傳遞過去
2、啟動ct1這個thread
3、無論是ct1的代碼流先被調度到執行還是UI主線程繼續執行都會進入t1或者t2的synchronized方法。
4、這裡假設ct1在start之後立馬被調度到並執行了t1的synchronized方法,然後sleep 10ms,此時再次發生調度。
5、UI主線程被再次調度到,然後執行t2的synchronized方法,sleep 10ms,再次調度到其它thread。
6、等到ct1的10ms sleep先結束之後再次調度到ct1,然後執行t2的synchronized方法,這裡會發生阻塞,因為在UI主線程中已經進入到了t2的synchronized方法,即t2執行個體自己的lock已經處於鎖定狀態,然後調度到其他thread。
7、等到UI主線程的10ms sleep結束之後再次調度到UI主線程,然後執行t1的synchronized的方法,這裡同樣會發生阻塞,因為在ct1中已經進入t1的synchronized方法,t1執行個體自己的lock已經處於鎖定狀態,然後調度到其他thread。
8、此時ct1和UI主線程已經產生相互依賴而死結。
將上面的代碼中使用的synchronized關鍵字更改為同步類的全域鎖,問題解決,activity不會再發生ANR,具體更改如下:
五、解決方案潛在的影響
由於使用類的全域鎖,而且沒有細分臨界區,所以在高並發的情況下可能會略微降低代碼執行流的輸送量,但是這個影響對SystemServer中AudioService的setindex和getindex等方法可以忽略,因為這幾個方法都非常輕量級並且並發量不會達到太高的量級。
Analyzed by vincent.song from SWD2 Framework team.
vincent.song@tcl.com
201506241646
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。