通过Android trace文件分析死锁ANR实例过程

对于从事Android开发的人来说,遇到ANR(Application Not Responding)是比较常见的问题。一般情况下,如果有ANR发生,系统都会在/data/anr/目录下生成trace文件,通过分析trace文件,可以定位产生ANR的原因。产生ANR的原因有很多,比如CPU使用过高、事件没有得到及时的响应、死锁等,下面将通过一次因为死锁导致的ANR问题,来说明如何通过trace文件分析ANR问题。

对应的部分trace文件内容如下:
"PowerManagerService" prio=5 tid=24 MONITOR
| group="main" sCount=1 dsCount=0 obj=0x41dd0eb0 self=0x5241b218
| sysTid=567 nice=0 sched=0/0 cgrp=apps handle=1380038664
| state=S schedstat=( 6682116007 11324451214 33313 ) utm=450 stm=219 core=1
at com.android.server.am.ActivityManagerService.broadcastIntent(ActivityManagerService.java:~13045)
- waiting to lock <0x41a874a0> (a com.android.server.am.ActivityManagerService) held by tid=12 (android.server.ServerThread)
at android.app.ContextImpl.sendBroadcast(ContextImpl.java:1144)
at com.android.server.power.PowerManagerService$DisplayBlankerImpl.unblankAllDisplays(PowerManagerService.java:3442)
at com.android.server.power.DisplayPowerState$PhotonicModulator$1.run(DisplayPowerState.java:456)
at android.os.Handler.handleCallback(Handler.java:800)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:194)
at android.os.HandlerThread.run(HandlerThread.java:60)
"Binder_B" prio=5 tid=85 MONITOR
| group="main" sCount=1 dsCount=0 obj=0x42744770 self=0x58329e88
| sysTid=3700 nice=-20 sched=0/0 cgrp=apps handle=1471424616
| state=S schedstat=( 1663727513 2044643318 6806 ) utm=132 stm=34 core=1
at com.android.server.power.PowerManagerService$DisplayBlankerImpl.toString(PowerManagerService.java:~3449)
- waiting to lock <0x41a7e420> (a com.android.server.power.PowerManagerService$DisplayBlankerImpl) held by tid=24 (PowerManagerService)
at java.lang.StringBuilder.append(StringBuilder.java:202)
at com.android.server.power.PowerManagerService.dump(PowerManagerService.java:3052)
at android.os.Binder.dump(Binder.java:264)
at android.os.Binder.onTransact(Binder.java:236)
at android.os.IPowerManager$Stub.onTransact(IPowerManager.java:373)
at android.os.Binder.execTransact(Binder.java:351)
at dalvik.system.NativeStart.run(Native Method)
"android.server.ServerThread" prio=5 tid=12 MONITOR
| group="main" sCount=1 dsCount=0 obj=0x41a76178 self=0x507837a8
| sysTid=545 nice=-2 sched=0/0 cgrp=apps handle=1349936616
| state=S schedstat=( 15368096286 21707846934 69485 ) utm=1226 stm=310 core=0
at com.android.server.power.PowerManagerService.isScreenOnInternal(PowerManagerService.java:~2529)
- waiting to lock <0x41a7e2e8> (a java.lang.Object) held by tid=85 (Binder_B)
at com.android.server.power.PowerManagerService.isScreenOn(PowerManagerService.java:2522)
at com.android.server.wm.WindowManagerService.sendScreenStatusToClientsLocked(WindowManagerService.java:7749)
at com.android.server.wm.WindowManagerService.setEventDispatching(WindowManagerService.java:7628)
at com.android.server.am.ActivityManagerService.updateEventDispatchingLocked(ActivityManagerService.java:8083)
at com.android.server.am.ActivityManagerService.wakingUp(ActivityManagerService.java:8077)
at com.android.server.power.Notifier.sendWakeUpBroadcast(Notifier.java:474)
at com.android.server.power.Notifier.sendNextBroadcast(Notifier.java:455)
at com.android.server.power.Notifier.access$700(Notifier.java:62)
at com.android.server.power.Notifier$NotifierHandler.handleMessage(Notifier.java:600)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:194)
at com.android.server.ServerThread.run(SystemServer.java:1328)
从trace文件看,是因为TID为24的线程等待一个TID为12的线程持有的锁,TID为12的线程等待一个TID为85的线程持有的锁,而TID为85的线程确等待一个TID为24的线程持有的锁,导致了循环等待的现象,对应的trace文件的语句如下:
TID 24:- waiting to lock <0x41a874a0> (a com.android.server.am.ActivityManagerService) held by tid=12 (android.server.ServerThread)
TID 12: - waiting to lock <0x41a7e2e8> (a java.lang.Object) held by tid=85 (Binder_B)
TID 85:- waiting to lock <0x41a7e420> (a com.android.server.power.PowerManagerService$DisplayBlankerImpl) held by tid=24 (PowerManagerService)
既然是死锁,那么先看各线程都有那些锁。
先看TID=24的线程的栈顶,ActivityManagerService的broadcastIntent函数代码如下:


代码如下:

public final int broadcastIntent(IApplicationThread caller,
Intent intent, String resolvedType, IIntentReceiver resultTo,
int resultCode, String resultData, Bundle map,
String requiredPermission, boolean serialized, boolean sticky, int userId) {
enforceNotIsolatedCaller("broadcastIntent");
synchronized(this) {
intent = verifyBroadcastLocked(intent);
final ProcessRecord callerApp = getRecordForAppLocked(caller);
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
int res = broadcastIntentLocked(callerApp,
callerApp != null ? callerApp.info.packageName : null,
intent, resolvedType, resultTo,
resultCode, resultData, map, requiredPermission, serialized, sticky,
callingPid, callingUid, userId);
Binder.restoreCallingIdentity(origId);
return res;
}

可以看到TID=24需要ActivityManagerService这个锁。再看TID=12线程的栈顶,PowerManagerService的isScreenOnInternal函数代码如下:


代码如下:

private boolean isScreenOnInternal() {
synchronized (mLock) {
return !mSystemReady
|| mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF;
}
}

可以看到需要PowerManagerService的mlock这个锁。最后看TID=85线程的栈顶,同样在PowerManagerService里面,内部类DisplayBlankerImpl的toString函数:


代码如下:

public String toString() {
synchronized (this) {
return "blanked=" + mBlanked;
}
}

这是在内部类DisplayBlankerImpl里面实现的,所以需要DisplayBlankerImpl这个锁。
对应的表格如下:
 
表一 各线程等待的锁情况
从表一来看,没有出现死锁现象,似乎并不是我们所想的那样。难道不是死锁?开始有点小怀疑自己了,难道别的原因导致的。也许只看调用堆栈的顶端可能不行,栈顶只能看出各线程需要的锁,不能仅看自己要什么吧!一味索取可不好!人不是这样做的!看一下整个的堆栈调用流程,看看自己拥有了那些锁。
跟踪TID=24线程的堆栈,在PowerManagerService内部类DisplayBlankerImpl的unblankAllDisplays函数中持有锁:


代码如下:

public void unblankAllDisplays() {
synchronized (this) {
nativeSetAutoSuspend(false);
nativeSetInteractive(true);
mDisplayManagerService.unblankAllDisplaysFromPowerManager();
mBlanked = false;
///M: add for tvout and hdmi
mTvOut.tvoutPowerEnable(true);
mHDMI.hdmiPowerEnable(true);
///@}
if (DEBUG) {
Slog.d(TAG_P, "unblankAllDisplays out ...");
}
if (mBootCompleted) {
Intent intent = new Intent(ACTION_LOCK_SCREEN_SHOW);
mContext.sendBroadcast(intent);
}
}
}

最后发送广播的代码,是我们自己添加的。根据unblankAllDisplays函数和broadcastIntent函数,可以看到TID=24的线程此时持有了DisplayBlankerImpl锁(unblankAllDisplays),等待ActivityManagerService锁(broadcastIntent)释放。
同样,跟踪TID=12线程的堆栈,在ActivityManagerService的wake_up函数中持有锁:


代码如下:

public void wakingUp() {
if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires permission "
+ android.Manifest.permission.DEVICE_POWER);
}
synchronized(this) {
Slog.i(TAG, "wakingUp");
mWentToSleep = false;
updateEventDispatchingLocked();
comeOutOfSleepIfNeededLocked();
}
}

根据wakingUp函数和isScreenOnInternal函数,可以看到TID=12的线程持有ActivityManagerService锁(wakingUp),等待PowerManagerService.mLock锁(isScreenOnInternal)。到这,似乎看到了希望,迷雾要拨开了,有点小自信是死锁导致的,但还不能最终下结论。
一鼓作气,跟踪TID=85线程的堆栈,在PowerManagerService的dump有持有锁的操作:
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
....
synchronized (mLock) {
...
}
根据toString函数和dump函数,可以看到TID=85线程此时持有PowerManagerService.mLock锁(dump),需要DisplayBlankerImpl(toString)。
似乎谜底已经揭晓了,如果你还没有看出来(其实我也没看出来),来个表看看吧!
 
表二 各线程锁的情况
清楚了吗?多么清晰的循环等待呀!死锁都死的这么完美,还是图表效果好,看来有时候在纸上画画还是有用的!

(0)

相关推荐

  • Android开发中避免应用无响应的方法(Application Not Responding、ANR)

    App里发生的最糟糕的事是弹出应用无响应"Application Not Responding" (ANR) 对话框.本课讲的是如何保持应用响应,避免ANR. 什么触发ANR 通常,系统会在应用无法对用户输入响应时显示ANR.比如,如果一个应用在I/O操作上阻塞了(频繁请求网络)UI线程,系统无法处理用户输入事件.或者,在UI线程中,app花了大量时间在构建复杂的类,或在游戏中计算下一个动作.保证这些操作高效是很重要的,但最高效的代码也需要花费时间. 在任何情况下,都不要在UI线程执行

  • Android ANR(Application Not Responding)的分析

    Android ANR(Application Not Responding)的分析 ANR (Application Not Responding) ANR定义:在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框.用户可以选择"等待"而让程序继续运行,也可以选择"强制关闭".所以一个流畅的合理的应用程序中不能出现anr,而让用户每

  • 通过Android trace文件分析死锁ANR实例过程

    对于从事Android开发的人来说,遇到ANR(Application Not Responding)是比较常见的问题.一般情况下,如果有ANR发生,系统都会在/data/anr/目录下生成trace文件,通过分析trace文件,可以定位产生ANR的原因.产生ANR的原因有很多,比如CPU使用过高.事件没有得到及时的响应.死锁等,下面将通过一次因为死锁导致的ANR问题,来说明如何通过trace文件分析ANR问题. 对应的部分trace文件内容如下: "PowerManagerService&qu

  • Android字符串资源文件format方法使用实例

    很多时候我们感性Google在设计Android时遵守了大量MVC架构方式,可以让写公共代码.美工和具体逻辑开发人员独立出来.有关Android 的资源文件values/strings.xml中如何实现格式化字符串呢? 这里Android123举个简单的例子,以及最终可能会用到哪些地方. 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?> <resources> <string name=

  • Android ANR分析trace文件的产生流程详情

    目录 前言 接着分析最后一步向收集到的进程发送信号 前言 首先收集需要dump trace的进程并给对应进程发送dump trace的信号 1.当一些带有超时机制的系统消息(如:Service的创建)判定超时后,会调用系统服务AMS接口,收集ANR相关信息并存档(data/anr/trace, data/system/dropbox) 2.进入到AMS中,AppError会先进行筛选(1.当前进程正在进行dump流程 2.已经发生crash 3. 已经被系统kill 4.系统是否正在关机等情况)

  • android 日志文件LogUtils实例

    背景 这是好久之前在网上找的一个常用类,已经忘记原文链接了,但是觉得很好用一直都在用,可以将日志写到file里面也可以定位你是在哪个类哪一行打印的日志,保存到文件的路径就是android/data/你的包名/files/目录下,然后我们就可以愉快的找问题了 import android.text.TextUtils; import android.util.Log; import com.smartlink.suixing.App; import com.smartlink.suixing.Bu

  • Android学习笔记(二)App工程文件分析

    App工程文件分析 关于如何创建一个最简单的Android App请参照链接: < Android学习笔记(一)环境安装及第一个hello world > http://www.jb51.net/article/52593.htm 创建完的工程文件如下图所示,本文对一些主要的文件进行分析. src文件分析 App源文件如图: 打开源文件 MainActivity.java 可看到如下代码: 源码主要功能如下: App源文件目录 package com.example.firstapp; 导入A

  • Android 图片的三级缓存机制实例分析

    Android 图片的三级缓存机制实例分析 当我们获取图片的时候,如果不加以协调好图片的缓存,就会造成大流量,费流量应用,用户体验不好,影响后期发展.为此,我特地分享Android图片的三级缓存机制之从网络中获取图片,来优化应用,具体分三步进行: (1)从缓存中获取图片 (2)从本地的缓存目录中获取图片,并且获取到之后,放到缓存中 (3)从网络去下载图片,下载完成之后,保存到本地和放到缓存中 很好的协调这三层图片缓存就可以大幅度提升应用的性能和用户体验. 快速实现三级缓存的工具类ImageCac

  • Android Beam 文件传输失败分析与解决方法

    最近在修改Android7.0原生平台的一些bug,其中有关Android Beam传输文件的一些问题还是蛮多的.所以特地找时间总结下曾经踏过的坑. 1.传输的文件名包含中文时,导致传输失败 可能是由于Google未考虑到本地化差异,导致在传输中文文件名的文件时直接提示传输失败. packages\apps\Nfc\src\com\android\nfc\beam\MimeTypeUtil.java 其实,上面忘了说了,只是从文件管理器中进入Android Beam分享才会出现上面的问题.因为当

  • Android中文件的压缩和解压缩实例代码

    使用场景 当我们在应用的Assets目录中需要加入文件时,可以直接将源文件放入,但这样会造成打包后的apk整体过大,此时就需要将放入的文件进行压缩.又如当我们需要从服务器中下载文件时,如果下载源文件耗时又消耗流量,较大文件需要压缩,可以使得传输效率大大提高.下面我们就学习下基本的文件压缩和解压缩.Java中提供了压缩和解压缩的输入输出流 public static void zip(String src,String dest) throwsIOException { //定义压缩输出流 Zip

  • 通过jstack分析解决进程死锁问题实例代码

    刚才用jstack解决了一个进程死锁的问题--其实早就解决了,也知道原因,只是一直没找到死锁的位置,不太甘心而已. 流程大致如下: (0)环境要求,JDK1.6及以上 (1)先找到进程的PID,Windows下,打开进程管理器,按照名字排序,可以找到叫做javaw.exe的进程(java虚拟机进程一律叫做javaw.exe),要找出哪个是你的进程,记住当前进程列表,然后重启你的进程,PID刷新过的那个即是你的进程. (2)在CMD下运行:jstack pid,jstack会在console上打出

  • Android 下载文件通知栏显示进度条功能的实例代码

    1.使用AsyncTask异步任务实现,调用publishProgress()方法刷新进度来实现(已优化) public class MyAsyncTask extends AsyncTask<String,Integer,Integer> { private Context context; private NotificationManager notificationManager; private NotificationCompat.Builder builder; public M

随机推荐