字节跳动 · Android 系统稳定性工程师 远程上机题
说明
- 上机时间90分钟,需全程开启屏幕共享,禁止查阅外部资料(可使用系统自带工具、Android源码文档);
- 所有操作需在 Android 真机(优先)或模拟器(API 30+)上完成,需提前准备 root 权限设备;
- 提交结果需包含:操作步骤、命令输出、问题分析报告、核心代码(若有);
- 重点考察:系统稳定性问题定位、调试工具使用、Framework 逻辑理解、Linux 命令实操,完全贴合岗位JD要求。
提交要求
- 所有操作步骤(含命令、代码)整理成文档,命名为“字节Android稳定性上机_姓名_日期.md”;
- 打包所有日志文件、代码文件、截图,命名为“上机提交材料_姓名_日期.zip”;
- 远程面试结束后,10分钟内提交至指定邮箱/链接;
- 面试时需现场演示关键操作(如日志抓取、工具调试),并讲解分析思路。
评分标准
- 操作规范性(20%):命令使用正确、步骤清晰、符合系统操作规范;
- 问题定位准确性(30%):能精准定位 ANR、NE、内存泄漏的根因,日志分析到位;
- 工具使用熟练度(20%):调试工具、Linux 命令使用熟练,能高效完成实操;
- 解决方案合理性(20%):规避方案、修复方案可行,贴合系统稳定性要求;
- 文档完整性(10%):提交材料完整,步骤、日志、代码、分析报告清晰。
题目一、基础操作(15分):系统日志抓取与分析
题目要求
- 抓取系统全量日志,包含 logcat(全缓冲区)、dmesg、tombstone(若有)、dropbox 日志,保存至 /sdcard/full_logs 目录,打包为 full_logs.tar.gz;
- 从日志中筛选出近10分钟内的 ANR、Crash 相关日志,整理成文本文件(标注日志类型、触发时间、核心信息);
- 使用 Linux 命令查看当前系统进程中,CPU 占用最高的前5个进程,截图并标注进程名称、PID、CPU 占用率。
考核要点
logcat、dmesg、adb 命令使用,日志筛选能力,Linux 基础命令(top、ps)实操。
二、核心实操(30分):ANR 问题定位与分析
题目要求
- 手动触发一次系统级 ANR(提示:可通过编写简单代码,让 SystemUI 主线程执行耗时操作,或频繁调用 AMS 接口);
- 抓取 ANR 触发时的 traces.txt、logcat、dmesg 日志;
- 分析日志,定位 ANR 触发的进程、线程、核心代码逻辑,明确 ANR 类型(主线程阻塞/Binder 等待/IO 阻塞);
- 给出临时规避方案和永久修复方案,说明方案原理。
补充提示
可使用 Android Studio 编写简单测试代码(如在 SystemUI 中添加一个按钮,点击后执行 Thread.sleep(10000)),触发 ANR;若无法修改 SystemUI,可触发第三方应用 ANR,重点分析日志定位逻辑。
考核要点
ANR 触发机制、traces.txt 分析能力、系统服务(AMS/WMS)交互逻辑、问题定位思路。
题目三、进阶实操(30分):Native Crash(NE)定位与调试
题目要求
- 编写一个简单的 JNI 程序(C/C++),故意触发 Native 内存越界(SIGSEGV),生成 tombstone 文件;
- 使用 addr2line、objdump 工具,解析 tombstone 中的崩溃地址,定位到具体的 C/C++ 代码行;
- 分析崩溃根因,修改代码修复崩溃,验证修复效果(重新运行,确认无 NE);
- 整理调试过程,包含:JNI 代码、tombstone 核心内容、工具解析命令及输出、修复前后对比。
考核要点
JNI 开发基础、NE 常见类型(SIGSEGV)、tombstone 解析、Native 调试工具(addr2line/objdump)使用。
题目四、综合分析(25分):系统内存泄漏定位
题目要求
- 模拟一个系统进程(如 SystemUI)的内存泄漏场景(提示:可通过静态变量持有 Context、未取消的广播接收器等方式);使用
- dumpsys meminfo、hprof 工具,抓取内存快照,分析内存泄漏的根因(定位到具体的泄漏对象、引用链);
- 使用 Linux 命令(procrank、sshot)监控内存变化,验证泄漏是否存在(多次采集内存数据,观察 PSS 值变化);
- 给出修复方案,修改代码后,重新监控内存,确认泄漏已解决,提交修复前后的内存数据对比。
考核要点
系统级内存泄漏与应用层泄漏的区别、内存分析工具(dumpsys meminfo/hprof)使用、内存泄漏定位思路、修复逻辑。
题目附加题(10分,可选):Watchdog 问题模拟与分析
题目要求
- 模拟 Watchdog 触发场景(提示:让 SystemServer 主线程阻塞超过60s,或让 AMS 与 WMS 发生死锁);
- 抓取 Watchdog 触发时的 dropbox 日志、traces.txt、dmesg 日志;
- 分析日志,明确 Watchdog 触发的核心原因(阻塞的服务、线程),给出解决方案。
一、基础操作(15分):系统日志抓取与分析
问题(题目要求)
- 抓取系统全量日志,包含 logcat(全缓冲区)、dmesg、tombstone(若有)、dropbox 日志,保存至 /sdcard/full_logs 目录,打包为 full_logs.tar.gz;
- 从日志中筛选出近10分钟内的 ANR、Crash 相关日志,整理成文本文件(标注日志类型、触发时间、核心信息);
- 使用 Linux 命令查看当前系统进程中,CPU 占用最高的前5个进程,截图并标注进程名称、PID、CPU 占用率。
操作示例(步骤+命令+输出)
步骤1:抓取全量日志并打包
# 1. 连接设备,获取root权限
adb root && adb remount
# 2. 创建日志保存目录
adb shell mkdir -p /sdcard/full_logs
# 3. 抓取logcat全缓冲区日志(带时间戳)
adb logcat -b all -v time -d > /sdcard/full_logs/logcat_all.log
# 4. 抓取dmesg内核日志
adb shell dmesg > /sdcard/full_logs/dmesg.log
# 5. 抓取tombstone日志(若存在)
adb pull /data/tombstones/ /sdcard/full_logs/tombstones/ 2>/dev/null || echo "无tombstone日志"
# 6. 抓取dropbox日志
adb pull /data/system/dropbox/ /sdcard/full_logs/dropbox/ 2>/dev/null || echo "无dropbox日志"
# 7. 打包日志
adb shell tar -zcvf /sdcard/full_logs.tar.gz /sdcard/full_logs/
# 8. 将打包文件拉取到本地(可选)
adb pull /sdcard/full_logs.tar.gz ./
步骤2:筛选ANR、Crash相关日志
# 1. 筛选logcat中的ANR日志,保存到anr_crash.log
adb shell grep -E "ANR in|Crash|Fatal Exception" /sdcard/full_logs/logcat_all.log > /sdcard/full_logs/anr_crash.log
# 2. 手动整理日志(示例,需结合实际日志补充)
# 编辑anr_crash.log,按以下格式标注:
# 日志类型:ANR
# 触发时间:2026-03-01 14:30:25
# 核心信息:ANR in com.android.systemui (com.android.systemui/.SystemUIService),原因:Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago.)
# 日志类型:Crash(JE)
# 触发时间:2026-03-01 14:35:10
# 核心信息:Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'void com.android.systemui.statusbar.StatusBarManager.updateNotification()' on a null object reference
步骤3:查看CPU占用最高的前5个进程
# 1. 实时查看CPU占用,按CPU排序,取前5个
adb shell top -m 5 -s cpu
# 2. 输出示例(截图并标注)
# PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
# 1234 root 20 0 2.5G 300M 150M R 98.0 5.2 10:30.50 com.example.testapp(PID:1234,CPU:98.0%,进程名:com.example.testapp)
# 567 system 20 0 3.2G 450M 200M R 85.0 7.8 8:45.20 com.android.systemui(PID:567,CPU:85.0%,进程名:com.android.systemui)
# 890 system 20 0 4.0G 600M 250M S 60.0 10.5 12:10.30 system_server(PID:890,CPU:60.0%,进程名:system_server)
# 1112 root 20 0 500M 80M 50M S 30.0 1.3 5:20.10 surfaceflinger(PID:1112,CPU:30.0%,进程名:surfaceflinger)
# 1314 app_123 20 0 1.8G 200M 100M S 25.0 3.5 3:15.40 com.android.settings(PID:1314,CPU:25.0%,进程名:com.android.settings)
答案整理(提交用)
日志抓取:已完成全量日志抓取,生成 full_logs.tar.gz,包含 logcat、dmesg、dropbox 日志(无tombstone日志);
ANR/Crash日志筛选:整理完成 anr_crash.log,标注日志类型、触发时间、核心信息(示例见操作步骤2);
CPU进程查看:已截图,标注5个高CPU进程的名称、PID、CPU占用率(示例见操作步骤3)。
考核要点
logcat、dmesg、adb 命令使用,日志筛选能力,Linux 基础命令(top、ps)实操。
二、核心实操(30分):ANR 问题定位与分析
问题(题目要求)
- 手动触发一次系统级 ANR(提示:可通过编写简单代码,让 SystemUI 主线程执行耗时操作,或频繁调用 AMS 接口);
- 抓取 ANR 触发时的 traces.txt、logcat、dmesg 日志;
- 分析日志,定位 ANR 触发的进程、线程、核心代码逻辑,明确 ANR 类型(主线程阻塞/Binder 等待/IO 阻塞);
- 给出临时规避方案和永久修复方案,说明方案原理。
操作示例(步骤+命令+代码+分析)
步骤1:手动触发系统级ANR(以SystemUI主线程耗时操作为例)
// 1. 编写简单测试代码(Android Studio,针对SystemUI,需系统权限)
// 在SystemUI的某个Activity/Fragment中添加按钮点击事件,执行耗时操作
public class SystemUIActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_system_ui);
Button btnTriggerANR = findViewById(R.id.btn_trigger_anr);
btnTriggerANR.setOnClickListener(v -> {
// 主线程执行耗时操作(10秒),触发ANR(Activity超时阈值5秒)
try {
Thread.sleep(10000); // 主线程阻塞10秒,超过5秒阈值
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
// 2. 编译代码,push到设备,替换SystemUI(需root)
adb push app-debug.apk /system/priv-app/SystemUI/
adb shell chmod 644 /system/priv-app/SystemUI/app-debug.apk
adb reboot
// 3. 重启后,打开SystemUI,点击按钮,等待5-10秒,触发ANR(弹出“系统无响应”提示)
步骤2:抓取ANR相关日志
# 1. 抓取traces.txt(ANR触发后自动生成)
adb pull /data/anr/traces.txt /sdcard/anr_traces.txt
# 2. 抓取logcat日志(重点筛选ANR相关)
adb logcat -b all -v time -d | grep -E "ANR|Input dispatching timed out" > /sdcard/anr_logcat.log
# 3. 抓取dmesg日志(排查内核层面异常)
adb shell dmesg > /sdcard/anr_dmesg.log
步骤3:日志分析,定位ANR根因
// 1. 分析traces.txt核心内容(关键片段)
----- pid 567 at 2026-03-01 14:40:20 -----
Cmd line: com.android.systemui
...
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x72d8a000 self=0x7f8a12345678
| sysTid=567 nice=0 cgrp=default sched=0/0 handle=0x7f8a12345678
| state=S schedstat=( 1000000000 200000000 300 ) utm=100 stm=0 core=0 HZ=100
| stack=0x7ffd12345678-0x7ffd12347890 stackSize=8192KB
| held mutexes=
at com.android.systemui.SystemUIActivity$1.onClick(SystemUIActivity.java:25)
- waiting on <0x72d8a000> (a android.view.View$OnClickListener)
at android.view.View.performClick(View.java:7448)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28305)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
// 2. 分析logcat核心内容
03-01 14:40:15.000 567 567 E InputDispatcher: Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago.)
03-01 14:40:20.000 890 900 E ActivityManager: ANR in com.android.systemui (com.android.systemui/.SystemUIActivity)
03-01 14:40:20.000 890 900 E ActivityManager: PID: 567
03-01 14:40:20.000 890 900 E ActivityManager: Reason: Input dispatching timed out (com.android.systemui/com.android.systemui.SystemUIActivity)
03-01 14:40:20.000 890 900 E ActivityManager: Load: 5.2 / 4.8 / 4.5
// 3. 定位结论
- 触发ANR的进程:com.android.systemui(PID:567)
- 触发ANR的线程:主线程(main tid=1)
- 核心代码逻辑:SystemUIActivity.java第25行,onClick方法中执行Thread.sleep(10000),主线程阻塞
- ANR类型:主线程阻塞(耗时操作导致)
步骤4:给出规避方案和修复方案
// 1. 临时规避方案
- 方案:强制杀死SystemUI进程,重启SystemUI,恢复系统操作
- 命令:adb shell am force-stop com.android.systemui
- 原理:杀死阻塞的进程,释放主线程资源,系统会自动重启SystemUI,临时恢复功能
// 2. 永久修复方案
- 方案:将主线程耗时操作移至子线程,避免阻塞主线程,增加超时兜底
- 修复后代码:
public class SystemUIActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_system_ui);
Button btnTriggerANR = findViewById(R.id.btn_trigger_anr);
btnTriggerANR.setOnClickListener(v -> {
// 子线程执行耗时操作
new Thread(() -> {
try {
Thread.sleep(10000);
// 耗时操作完成后,通过Handler更新UI(避免子线程操作UI)
runOnUiThread(() -> {
Toast.makeText(SystemUIActivity.this, "耗时操作完成", Toast.LENGTH_SHORT).show();
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
});
}
}
- 原理:子线程负责执行耗时操作,主线程正常处理用户交互和系统消息,避免触发ANR;通过Handler实现子线程与主线程通信,确保UI操作安全。
答案整理(提交用)
ANR触发:已通过修改SystemUI代码,让主线程执行Thread.sleep(10000),成功触发系统级ANR;
日志抓取:已抓取traces.txt、logcat、dmesg日志,保存至对应目录;
根因分析:ANR触发进程为com.android.systemui(PID:567),主线程因执行耗时操作(Thread.sleep(10000))阻塞,属于主线程阻塞型ANR;
解决方案:临时规避方案为强制杀死SystemUI进程并重启;永久修复方案为将耗时操作移至子线程,通过Handler更新UI,避免主线程阻塞。
补充提示
可使用 Android Studio 编写简单测试代码(如在 SystemUI 中添加一个按钮,点击后执行 Thread.sleep(10000)),触发 ANR;若无法修改 SystemUI,可触发第三方应用 ANR,重点分析日志定位逻辑。
考核要点
ANR 触发机制、traces.txt 分析能力、系统服务(AMS/WMS)交互逻辑、问题定位思路。
三、进阶实操(30分):Native Crash(NE)定位与调试
问题(题目要求)
- 编写一个简单的 JNI 程序(C/C++),故意触发 Native 内存越界(SIGSEGV),生成 tombstone 文件;
- 使用 addr2line、objdump 工具,解析 tombstone 中的崩溃地址,定位到具体的 C/C++ 代码行;
- 分析崩溃根因,修改代码修复崩溃,验证修复效果(重新运行,确认无 NE);
- 整理调试过程,包含:JNI 代码、tombstone 核心内容、工具解析命令及输出、修复前后对比。
操作示例(步骤+代码+命令+分析)
步骤1:编写JNI程序,触发Native内存越界(SIGSEGV)
// 1. Java层代码(MainActivity.java)
public class MainActivity extends AppCompatActivity {
// 加载Native库
static {
System.loadLibrary("native-lib");
}
// Native方法,触发内存越界
public native void triggerNativeCrash();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 调用Native方法,触发崩溃
triggerNativeCrash();
}
}
// 2. C/C++代码(native-lib.cpp)
#include <jni.h>
#include <string>
extern "C" JNIEXPORT void JNICALL
Java_com_example_nativecrash_MainActivity_triggerNativeCrash(JNIEnv* env, jobject thiz) {
// 故意触发内存越界:定义长度为5的数组,访问第10个元素(下标9)
int arr[5] = {1, 2, 3, 4, 5};
int value = arr[9]; // 内存越界,触发SIGSEGV
}
// 3. 配置CMakeLists.txt(确保生成so库,带符号表)
cmake_minimum_required(VERSION 3.18.1)
project("nativecrash")
add_library(
native-lib
SHARED
native-lib.cpp)
find_library(
log-lib
log)
target_link_libraries(
native-lib
${log-lib})
// 4. 编译项目,生成APK,安装到设备并运行,触发Native Crash(生成tombstone)
步骤2:抓取tombstone文件,解析崩溃地址
# 1. 抓取tombstone文件(崩溃后自动生成)
adb pull /data/tombstones/tombstone_00 /sdcard/tombstone_00
# 2. 查看tombstone核心内容(定位崩溃地址)
adb shell cat /sdcard/tombstone_00 | grep -E "signal|pc:"
# 输出示例:
# signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x7f8a12345678
# pc 0x7f8a12345678 /data/app/~~abc123/com.example.nativecrash/lib/arm64/libnative-lib.so (Java_com_example_nativecrash_MainActivity_triggerNativeCrash+28)
# 3. 使用addr2line解析崩溃地址(需带符号表的so库,编译时默认生成)
# 先将so库拉取到本地
adb pull /data/app/~~abc123/com.example.nativecrash/lib/arm64/libnative-lib.so ./
# 执行addr2line命令(替换为实际的崩溃地址和so路径)
addr2line -f -e libnative-lib.so 0x7f8a12345678
# 输出示例(定位到具体代码行):
# Java_com_example_nativecrash_MainActivity_triggerNativeCrash
# /home/user/AndroidStudioProjects/NativeCrash/app/src/main/cpp/native-lib.cpp:12
# 4. 使用objdump查看so库汇编代码(验证崩溃位置)
objdump -d libnative-lib.so | grep -A 10 "Java_com_example_nativecrash_MainActivity_triggerNativeCrash"
# 输出示例(可看到数组访问的汇编指令)
步骤3:分析根因,修改代码修复崩溃
// 1. 崩溃根因分析
- 崩溃信号:SIGSEGV(段错误),原因是访问了非法内存地址(内存越界);
- 具体原因:C++代码中定义了长度为5的数组arr[5],下标范围为0-4,却访问了arr[9],导致内存越界;
- 崩溃位置:native-lib.cpp第12行,int value = arr[9];
// 2. 修复代码(native-lib.cpp)
extern "C" JNIEXPORT void JNICALL
Java_com_example_nativecrash_MainActivity_triggerNativeCrash(JNIEnv* env, jobject thiz) {
int arr[5] = {1, 2, 3, 4, 5};
// 修复:增加边界判断,避免访问超出数组范围的元素
int index = 9;
int value = 0;
if (index < 5) {
value = arr[index];
} else {
// 异常处理,避免崩溃
__android_log_print(ANDROID_LOG_ERROR, "NativeCrash", "index out of bounds: %d", index);
}
}
// 3. 验证修复效果
- 重新编译项目,安装APK并运行;
- 查看logcat日志,无SIGSEGV崩溃信息,日志打印"index out of bounds: 9";
- 确认无tombstone文件生成,修复成功。
步骤4:整理调试过程
// 1. JNI代码(修复前)
见步骤1中的native-lib.cpp(未做边界判断,访问arr[9]);
// 2. tombstone核心内容
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x7f8a12345678
pc 0x7f8a12345678 /data/app/~~abc123/com.example.nativecrash/lib/arm64/libnative-lib.so (Java_com_example_nativecrash_MainActivity_triggerNativeCrash+28)
// 3. 工具解析命令及输出
- 命令:addr2line -f -e libnative-lib.so 0x7f8a12345678
- 输出:
Java_com_example_nativecrash_MainActivity_triggerNativeCrash
/home/user/AndroidStudioProjects/NativeCrash/app/src/main/cpp/native-lib.cpp:12
// 4. 修复前后对比
- 修复前:运行APK触发SIGSEGV崩溃,生成tombstone文件;
- 修复后:运行APK无崩溃,logcat打印异常提示,无tombstone文件生成。
答案整理(提交用)
JNI程序编写:已完成JNI程序编写,故意触发内存越界,成功生成tombstone文件;
崩溃解析:使用addr2line工具定位到崩溃位置为native-lib.cpp第12行,崩溃根因为数组访问越界(SIGSEGV);
修复验证:增加数组边界判断,修复后运行无崩溃,logcat打印异常提示,验证修复成功;
调试过程:整理完成JNI代码、tombstone核心内容、工具解析命令及输出、修复前后对比(见操作步骤4)。
考核要点
JNI 开发基础、NE 常见类型(SIGSEGV)、tombstone 解析、Native 调试工具(addr2line/objdump)使用。
四、综合分析(25分):系统内存泄漏定位
问题(题目要求)
- 模拟一个系统进程(如 SystemUI)的内存泄漏场景(提示:可通过静态变量持有 Context、未取消的广播接收器等方式);
- 使用 dumpsys meminfo、hprof 工具,抓取内存快照,分析内存泄漏的根因(定位到具体的泄漏对象、引用链);
- 使用 Linux 命令(procrank、sshot)监控内存变化,验证泄漏是否存在(多次采集内存数据,观察 PSS 值变化);
- 给出修复方案,修改代码后,重新监控内存,确认泄漏已解决,提交修复前后的内存数据对比。
操作示例(步骤+代码+命令+分析)
步骤1:模拟SystemUI内存泄漏(静态变量持有Context)
// 1. 编写SystemUI测试代码,模拟内存泄漏
public class LeakService extends Service {
// 静态变量持有Context(SystemUI的Context),导致Context无法回收,引发内存泄漏
private static Context sLeakContext;
@Override
public void onCreate() {
super.onCreate();
// 静态变量持有当前Service的Context(SystemUI进程的Context)
sLeakContext = this;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
// 2. 在SystemUI的AndroidManifest.xml中注册Service
<service android:name=".LeakService" android:process="com.android.systemui"/>
// 3. 启动Service,模拟泄漏场景
adb shell am startservice -n com.android.systemui/.LeakService
// 4. 多次启动/停止Service,观察内存变化(泄漏会导致内存持续上升)
步骤2:使用工具抓取内存快照,分析泄漏根因
# 1. 使用dmesg meminfo查看SystemUI内存状态(多次采集)
# 第一次采集(启动Service前)
adb shell dumpsys meminfo com.android.systemui | grep -E "Total PSS|Leaked"
# 输出示例:Total PSS: 250,000 KB
# 第二次采集(启动/停止Service 5次后)
adb shell dumpsys meminfo com.android.systemui | grep -E "Total PSS|Leaked"
# 输出示例:Total PSS: 350,000 KB(内存明显上升,无下降)
# 2. 抓取hprof内存快照(Java层内存泄漏)
adb shell am dumpheap com.android.systemui /sdcard/systemui_heap.hprof
adb pull /sdcard/systemui_heap.hprof ./
# 3. 使用Android Studio Profiler分析hprof快照
- 打开Android Studio,导入hprof文件;
- 选择“Leak Canary”或“Memory Analyzer”,查看泄漏对象;
- 分析结果:LeakService的sLeakContext静态变量持有Context对象,导致Context无法被GC回收,形成内存泄漏;
- 引用链:sLeakContext(静态变量)→ LeakService(Context)→ SystemUI进程,导致LeakService实例无法回收。
# 4. 使用procrank监控内存变化(多次采集PSS值)
adb shell procrank | grep com.android.systemui
# 采集结果示例:
# 第一次:com.android.systemui (pid 567) PSS: 250000 KB
# 第二次:com.android.systemui (pid 567) PSS: 280000 KB
# 第三次:com.android.systemui (pid 567) PSS: 320000 KB
# 第四次:com.android.systemui (pid 567) PSS: 350000 KB
# 结论:PSS值持续上升,无下降,确认存在内存泄漏。
步骤3:修复内存泄漏,验证效果
// 1. 修复方案:取消静态变量对Context的持有,在Service销毁时释放引用
public class LeakService extends Service {
// 取消静态修饰,或在销毁时置空
private Context mContext; // 改为非静态变量
@Override
public void onCreate() {
super.onCreate();
mContext = this;
}
@Override
public void onDestroy() {
super.onDestroy();
// 销毁时释放引用,避免泄漏
mContext = null;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
// 2. 重新编译,替换SystemUI,重启设备
adb push app-debug.apk /system/priv-app/SystemUI/
adb shell chmod 644 /system/priv-app/SystemUI/app-debug.apk
adb reboot
// 3. 重新模拟场景,监控内存变化
adb shell am startservice -n com.android.systemui/.LeakService
adb shell am stopservice -n com.android.systemui/.LeakService
// 多次重复,采集内存数据
# 4. 验证结果(procrank采集PSS值)
# 第一次:com.android.systemui (pid 567) PSS: 250000 KB
# 第二次:com.android.systemui (pid 567) PSS: 260000 KB(启动后上升)
# 第三次:com.android.systemui (pid 567) PSS: 252000 KB(停止后下降)
# 第四次:com.android.systemui (pid 567) PSS: 251000 KB(趋于稳定)
# 结论:内存不再持续上升,停止Service后内存下降,泄漏已解决。
# 5. hprof验证:重新抓取hprof,无泄漏对象提示。
步骤4:整理内存数据对比
// 修复前内存数据(PSS值)
1. 启动Service前:250,000 KB
2. 启动/停止Service 2次后:280,000 KB
3. 启动/停止Service 5次后:350,000 KB
4. 特征:持续上升,无下降趋势,存在明显内存泄漏。
// 修复后内存数据(PSS值)
1. 启动Service前:250,000 KB
2. 启动/停止Service 2次后:260,000 KB(启动上升,停止下降)
3. 启动/停止Service 5次后:251,000 KB(趋于稳定)
4. 特征:无持续上升,停止Service后内存回收,泄漏已解决。
答案整理(提交用)
泄漏模拟:已通过SystemUI的LeakService,用静态变量持有Context,成功模拟系统进程内存泄漏;
泄漏分析:通过dumpsys meminfo、hprof工具,定位到泄漏根因为静态变量sLeakContext持有Context,导致LeakService实例无法回收,引用链清晰;
内存监控:使用procrank多次采集PSS值,修复前内存持续上升,确认泄漏存在;
修复验证:将静态变量改为非静态,在Service销毁时置空引用,修复后内存趋于稳定,无持续上升,确认泄漏已解决;提交修复前后内存数据对比(见操作步骤4)。
考核要点
系统级内存泄漏与应用层泄漏的区别、内存分析工具(dumpsys meminfo/hprof)使用、内存泄漏定位思路、修复逻辑。
附加题(10分,可选):Watchdog 问题模拟与分析
问题(题目要求)
- 模拟 Watchdog 触发场景(提示:让 SystemServer 主线程阻塞超过60s,或让 AMS 与 WMS 发生死锁);
- 抓取 Watchdog 触发时的 dropbox 日志、traces.txt、dmesg 日志;
- 分析日志,明确 Watchdog 触发的核心原因(阻塞的服务、线程),给出解决方案。
操作示例(步骤+代码+命令+分析)
步骤1:模拟Watchdog触发(SystemServer主线程阻塞超过60s)
// 1. 编写代码,修改SystemServer主线程逻辑(需系统权限,修改Framework源码)
// 在SystemServer的startOtherServices()方法中,添加耗时操作(70秒)
public class SystemServer {
private void startOtherServices() {
// 其他服务启动逻辑...
// 故意添加耗时操作,阻塞SystemServer主线程(70秒,超过Watchdog 60秒阈值)
try {
Thread.sleep(70000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 后续服务启动逻辑...
}
}
// 2. 编译Framework源码,刷入设备,重启设备
// 重启后,SystemServer启动时,主线程阻塞70秒,触发Watchdog
// 3. 观察设备:设备会重启,重启前logcat会打印Watchdog超时日志
步骤2:抓取Watchdog相关日志
# 1. 抓取dropbox日志(Watchdog触发后自动生成)
adb pull /data/system/dropbox/watchdog-xxx.txt /sdcard/watchdog_dropbox.txt
# 2. 抓取traces.txt(Watchdog触发时生成)
adb pull /data/anr/traces.txt /sdcard/watchdog_traces.txt
# 3. 抓取dmesg日志(排查内核层面)
adb shell dmesg > /sdcard/watchdog_dmesg.log
步骤3:日志分析,定位Watchdog根因
// 1. dropbox日志核心内容
Watchdog timeout: Service AMS is not responding
SystemServer main thread blocked for 70 seconds
PID: 890 (system_server)
Reason: SystemServer main thread blocked in Thread.sleep(70000)
// 2. traces.txt核心内容
----- pid 890 at 2026-03-01 15:10:30 -----
Cmd line: system_server
...
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x72d8a000 self=0x7f8a12345678
| sysTid=890 nice=0 cgrp=default sched=0/0 handle=0x7f8a12345678
| state=S schedstat=( 70000000000 200000000 300 ) utm=700 stm=0 core=0 HZ=100
| stack=0x7ffd12345678-0x7ffd12347890 stackSize=8192KB
| held mutexes=
at com.android.server.SystemServer.startOtherServices(SystemServer.java:2345)
- waiting on <0x72d8a000> (a java.lang.Object)
at com.android.server.SystemServer.run(SystemServer.java:456)
at com.android.server.SystemServer.main(SystemServer.java:300)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
// 3. 定位结论
- Watchdog触发原因:SystemServer主线程(PID:890)阻塞超过60秒(实际阻塞70秒);
- 阻塞位置:SystemServer.java第2345行,startOtherServices()方法中的Thread.sleep(70000);
- 涉及服务:AMS(因SystemServer主线程阻塞,无法响应Watchdog心跳)。
// 4. 解决方案
- 临时方案:重启设备,SystemServer重新启动,临时恢复系统;
- 永久方案:将SystemServer主线程中的耗时操作移至子线程,避免阻塞主线程,确保Watchdog心跳正常响应;
- 修复代码:在SystemServer的startOtherServices()方法中,将耗时操作放入子线程执行。
答案整理(提交用)
Watchdog模拟:已通过修改SystemServer源码,在主线程添加70秒耗时操作,成功触发Watchdog,设备重启;
日志抓取:已抓取dropbox、traces.txt、dmesg日志,记录Watchdog触发细节;
根因分析:Watchdog触发核心原因是SystemServer主线程阻塞超过60秒,阻塞位置为startOtherServices()方法的Thread.sleep(70000),导致AMS无法响应Watchdog心跳;
解决方案:临时方案为重启设备;永久方案为将耗时操作移至子线程,避免主线程阻塞,确保Watchdog正常监控。