rokevin
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
  • 字节跳动 · Android 系统稳定性工程师 远程上机题

  • 说明
  • 提交要求
  • 评分标准
  • 题目一、基础操作(15分):系统日志抓取与分析
    • 题目要求
    • 考核要点
  • 二、核心实操(30分):ANR 问题定位与分析
    • 题目要求
    • 补充提示
    • 考核要点
  • 题目三、进阶实操(30分):Native Crash(NE)定位与调试
    • 题目要求
    • 考核要点
  • 题目四、综合分析(25分):系统内存泄漏定位
    • 题目要求
    • 考核要点
  • 题目附加题(10分,可选):Watchdog 问题模拟与分析
    • 题目要求
  • 一、基础操作(15分):系统日志抓取与分析
    • 问题(题目要求)
    • 操作示例(步骤+命令+输出)
      • 步骤1:抓取全量日志并打包
      • 步骤2:筛选ANR、Crash相关日志
      • 步骤3:查看CPU占用最高的前5个进程
    • 答案整理(提交用)
    • 考核要点
  • 二、核心实操(30分):ANR 问题定位与分析
    • 问题(题目要求)
    • 操作示例(步骤+命令+代码+分析)
      • 步骤1:手动触发系统级ANR(以SystemUI主线程耗时操作为例)
      • 步骤2:抓取ANR相关日志
      • 步骤3:日志分析,定位ANR根因
      • 步骤4:给出规避方案和修复方案
    • 答案整理(提交用)
    • 补充提示
    • 考核要点
  • 三、进阶实操(30分):Native Crash(NE)定位与调试
    • 问题(题目要求)
    • 操作示例(步骤+代码+命令+分析)
      • 步骤1:编写JNI程序,触发Native内存越界(SIGSEGV)
      • 步骤2:抓取tombstone文件,解析崩溃地址
      • 步骤3:分析根因,修改代码修复崩溃
      • 步骤4:整理调试过程
    • 答案整理(提交用)
    • 考核要点
  • 四、综合分析(25分):系统内存泄漏定位
    • 问题(题目要求)
    • 操作示例(步骤+代码+命令+分析)
      • 步骤1:模拟SystemUI内存泄漏(静态变量持有Context)
      • 步骤2:使用工具抓取内存快照,分析泄漏根因
      • 步骤3:修复内存泄漏,验证效果
      • 步骤4:整理内存数据对比
    • 答案整理(提交用)
    • 考核要点
  • 附加题(10分,可选):Watchdog 问题模拟与分析
    • 问题(题目要求)
    • 操作示例(步骤+代码+命令+分析)
      • 步骤1:模拟Watchdog触发(SystemServer主线程阻塞超过60s)
      • 步骤2:抓取Watchdog相关日志
      • 步骤3:日志分析,定位Watchdog根因
    • 答案整理(提交用)

字节跳动 · Android 系统稳定性工程师 远程上机题

说明

  1. 上机时间90分钟,需全程开启屏幕共享,禁止查阅外部资料(可使用系统自带工具、Android源码文档);
  2. 所有操作需在 Android 真机(优先)或模拟器(API 30+)上完成,需提前准备 root 权限设备;
  3. 提交结果需包含:操作步骤、命令输出、问题分析报告、核心代码(若有);
  4. 重点考察:系统稳定性问题定位、调试工具使用、Framework 逻辑理解、Linux 命令实操,完全贴合岗位JD要求。

提交要求

  1. 所有操作步骤(含命令、代码)整理成文档,命名为“字节Android稳定性上机_姓名_日期.md”;
  2. 打包所有日志文件、代码文件、截图,命名为“上机提交材料_姓名_日期.zip”;
  3. 远程面试结束后,10分钟内提交至指定邮箱/链接;
  4. 面试时需现场演示关键操作(如日志抓取、工具调试),并讲解分析思路。

评分标准

  • 操作规范性(20%):命令使用正确、步骤清晰、符合系统操作规范;
  • 问题定位准确性(30%):能精准定位 ANR、NE、内存泄漏的根因,日志分析到位;
  • 工具使用熟练度(20%):调试工具、Linux 命令使用熟练,能高效完成实操;
  • 解决方案合理性(20%):规避方案、修复方案可行,贴合系统稳定性要求;
  • 文档完整性(10%):提交材料完整,步骤、日志、代码、分析报告清晰。

题目一、基础操作(15分):系统日志抓取与分析

题目要求

  1. 抓取系统全量日志,包含 logcat(全缓冲区)、dmesg、tombstone(若有)、dropbox 日志,保存至 /sdcard/full_logs 目录,打包为 full_logs.tar.gz;
  2. 从日志中筛选出近10分钟内的 ANR、Crash 相关日志,整理成文本文件(标注日志类型、触发时间、核心信息);
  3. 使用 Linux 命令查看当前系统进程中,CPU 占用最高的前5个进程,截图并标注进程名称、PID、CPU 占用率。

考核要点

logcat、dmesg、adb 命令使用,日志筛选能力,Linux 基础命令(top、ps)实操。

二、核心实操(30分):ANR 问题定位与分析

题目要求

  1. 手动触发一次系统级 ANR(提示:可通过编写简单代码,让 SystemUI 主线程执行耗时操作,或频繁调用 AMS 接口);
  2. 抓取 ANR 触发时的 traces.txt、logcat、dmesg 日志;
  3. 分析日志,定位 ANR 触发的进程、线程、核心代码逻辑,明确 ANR 类型(主线程阻塞/Binder 等待/IO 阻塞);
  4. 给出临时规避方案和永久修复方案,说明方案原理。

补充提示

可使用 Android Studio 编写简单测试代码(如在 SystemUI 中添加一个按钮,点击后执行 Thread.sleep(10000)),触发 ANR;若无法修改 SystemUI,可触发第三方应用 ANR,重点分析日志定位逻辑。

考核要点

ANR 触发机制、traces.txt 分析能力、系统服务(AMS/WMS)交互逻辑、问题定位思路。

题目三、进阶实操(30分):Native Crash(NE)定位与调试

题目要求

  1. 编写一个简单的 JNI 程序(C/C++),故意触发 Native 内存越界(SIGSEGV),生成 tombstone 文件;
  2. 使用 addr2line、objdump 工具,解析 tombstone 中的崩溃地址,定位到具体的 C/C++ 代码行;
  3. 分析崩溃根因,修改代码修复崩溃,验证修复效果(重新运行,确认无 NE);
  4. 整理调试过程,包含:JNI 代码、tombstone 核心内容、工具解析命令及输出、修复前后对比。

考核要点

JNI 开发基础、NE 常见类型(SIGSEGV)、tombstone 解析、Native 调试工具(addr2line/objdump)使用。

题目四、综合分析(25分):系统内存泄漏定位

题目要求

  1. 模拟一个系统进程(如 SystemUI)的内存泄漏场景(提示:可通过静态变量持有 Context、未取消的广播接收器等方式);使用
  2. dumpsys meminfo、hprof 工具,抓取内存快照,分析内存泄漏的根因(定位到具体的泄漏对象、引用链);
  3. 使用 Linux 命令(procrank、sshot)监控内存变化,验证泄漏是否存在(多次采集内存数据,观察 PSS 值变化);
  4. 给出修复方案,修改代码后,重新监控内存,确认泄漏已解决,提交修复前后的内存数据对比。

考核要点

系统级内存泄漏与应用层泄漏的区别、内存分析工具(dumpsys meminfo/hprof)使用、内存泄漏定位思路、修复逻辑。

题目附加题(10分,可选):Watchdog 问题模拟与分析

题目要求

  1. 模拟 Watchdog 触发场景(提示:让 SystemServer 主线程阻塞超过60s,或让 AMS 与 WMS 发生死锁);
  2. 抓取 Watchdog 触发时的 dropbox 日志、traces.txt、dmesg 日志;
  3. 分析日志,明确 Watchdog 触发的核心原因(阻塞的服务、线程),给出解决方案。

一、基础操作(15分):系统日志抓取与分析

问题(题目要求)

  1. 抓取系统全量日志,包含 logcat(全缓冲区)、dmesg、tombstone(若有)、dropbox 日志,保存至 /sdcard/full_logs 目录,打包为 full_logs.tar.gz;
  2. 从日志中筛选出近10分钟内的 ANR、Crash 相关日志,整理成文本文件(标注日志类型、触发时间、核心信息);
  3. 使用 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)

答案整理(提交用)

  1. 日志抓取:已完成全量日志抓取,生成 full_logs.tar.gz,包含 logcat、dmesg、dropbox 日志(无tombstone日志);

  2. ANR/Crash日志筛选:整理完成 anr_crash.log,标注日志类型、触发时间、核心信息(示例见操作步骤2);

  3. CPU进程查看:已截图,标注5个高CPU进程的名称、PID、CPU占用率(示例见操作步骤3)。

考核要点

logcat、dmesg、adb 命令使用,日志筛选能力,Linux 基础命令(top、ps)实操。

二、核心实操(30分):ANR 问题定位与分析

问题(题目要求)

  1. 手动触发一次系统级 ANR(提示:可通过编写简单代码,让 SystemUI 主线程执行耗时操作,或频繁调用 AMS 接口);
  2. 抓取 ANR 触发时的 traces.txt、logcat、dmesg 日志;
  3. 分析日志,定位 ANR 触发的进程、线程、核心代码逻辑,明确 ANR 类型(主线程阻塞/Binder 等待/IO 阻塞);
  4. 给出临时规避方案和永久修复方案,说明方案原理。

操作示例(步骤+命令+代码+分析)

步骤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操作安全。

答案整理(提交用)

  1. ANR触发:已通过修改SystemUI代码,让主线程执行Thread.sleep(10000),成功触发系统级ANR;

  2. 日志抓取:已抓取traces.txt、logcat、dmesg日志,保存至对应目录;

  3. 根因分析:ANR触发进程为com.android.systemui(PID:567),主线程因执行耗时操作(Thread.sleep(10000))阻塞,属于主线程阻塞型ANR;

  4. 解决方案:临时规避方案为强制杀死SystemUI进程并重启;永久修复方案为将耗时操作移至子线程,通过Handler更新UI,避免主线程阻塞。

补充提示

可使用 Android Studio 编写简单测试代码(如在 SystemUI 中添加一个按钮,点击后执行 Thread.sleep(10000)),触发 ANR;若无法修改 SystemUI,可触发第三方应用 ANR,重点分析日志定位逻辑。

考核要点

ANR 触发机制、traces.txt 分析能力、系统服务(AMS/WMS)交互逻辑、问题定位思路。

三、进阶实操(30分):Native Crash(NE)定位与调试

问题(题目要求)

  1. 编写一个简单的 JNI 程序(C/C++),故意触发 Native 内存越界(SIGSEGV),生成 tombstone 文件;
  2. 使用 addr2line、objdump 工具,解析 tombstone 中的崩溃地址,定位到具体的 C/C++ 代码行;
  3. 分析崩溃根因,修改代码修复崩溃,验证修复效果(重新运行,确认无 NE);
  4. 整理调试过程,包含: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文件生成。

答案整理(提交用)

  1. JNI程序编写:已完成JNI程序编写,故意触发内存越界,成功生成tombstone文件;

  2. 崩溃解析:使用addr2line工具定位到崩溃位置为native-lib.cpp第12行,崩溃根因为数组访问越界(SIGSEGV);

  3. 修复验证:增加数组边界判断,修复后运行无崩溃,logcat打印异常提示,验证修复成功;

  4. 调试过程:整理完成JNI代码、tombstone核心内容、工具解析命令及输出、修复前后对比(见操作步骤4)。

考核要点

JNI 开发基础、NE 常见类型(SIGSEGV)、tombstone 解析、Native 调试工具(addr2line/objdump)使用。

四、综合分析(25分):系统内存泄漏定位

问题(题目要求)

  1. 模拟一个系统进程(如 SystemUI)的内存泄漏场景(提示:可通过静态变量持有 Context、未取消的广播接收器等方式);
  2. 使用 dumpsys meminfo、hprof 工具,抓取内存快照,分析内存泄漏的根因(定位到具体的泄漏对象、引用链);
  3. 使用 Linux 命令(procrank、sshot)监控内存变化,验证泄漏是否存在(多次采集内存数据,观察 PSS 值变化);
  4. 给出修复方案,修改代码后,重新监控内存,确认泄漏已解决,提交修复前后的内存数据对比。

操作示例(步骤+代码+命令+分析)

步骤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后内存回收,泄漏已解决。

答案整理(提交用)

  1. 泄漏模拟:已通过SystemUI的LeakService,用静态变量持有Context,成功模拟系统进程内存泄漏;

  2. 泄漏分析:通过dumpsys meminfo、hprof工具,定位到泄漏根因为静态变量sLeakContext持有Context,导致LeakService实例无法回收,引用链清晰;

  3. 内存监控:使用procrank多次采集PSS值,修复前内存持续上升,确认泄漏存在;

  4. 修复验证:将静态变量改为非静态,在Service销毁时置空引用,修复后内存趋于稳定,无持续上升,确认泄漏已解决;提交修复前后内存数据对比(见操作步骤4)。

考核要点

系统级内存泄漏与应用层泄漏的区别、内存分析工具(dumpsys meminfo/hprof)使用、内存泄漏定位思路、修复逻辑。

附加题(10分,可选):Watchdog 问题模拟与分析

问题(题目要求)

  1. 模拟 Watchdog 触发场景(提示:让 SystemServer 主线程阻塞超过60s,或让 AMS 与 WMS 发生死锁);
  2. 抓取 Watchdog 触发时的 dropbox 日志、traces.txt、dmesg 日志;
  3. 分析日志,明确 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()方法中,将耗时操作放入子线程执行。

答案整理(提交用)

  1. Watchdog模拟:已通过修改SystemServer源码,在主线程添加70秒耗时操作,成功触发Watchdog,设备重启;

  2. 日志抓取:已抓取dropbox、traces.txt、dmesg日志,记录Watchdog触发细节;

  3. 根因分析:Watchdog触发核心原因是SystemServer主线程阻塞超过60秒,阻塞位置为startOtherServices()方法的Thread.sleep(70000),导致AMS无法响应Watchdog心跳;

  4. 解决方案:临时方案为重启设备;永久方案为将耗时操作移至子线程,避免主线程阻塞,确保Watchdog正常监控。

最近更新:: 2026/3/3 19:05
Contributors: luokaiwen