15、Logcat 与性能分析:GC 日志、CPU 负载、内存抖动的识别

性能分析,说白了就是找茬。找系统运行时的各种不顺畅。

Logcat 在这块儿是个宝。很多开发者觉得它只是看报错信息的,其实不然。我个人习惯,在性能调优阶段,Logcat 是我第一个打开的工具。它能直接告诉你系统在「喘气」还是「卡壳」。

15.1 GC 日志:读懂系统的「呼吸」

GC,垃圾回收。Java 和 Kotlin 的世界里,内存回收是自动的。但自动不代表免费。GC 发生时,所有线程都可能被暂停——这就是传说中的「STW」(Stop The World)。

怎么在 Logcat 里看 GC?很简单。过滤关键字 GC 或者 art。你会看到类似这样的输出:

I/art: Explicit concurrent mark sweep GC freed 2047(64KB) AllocSpace objects, 0(0B) LOS objects, 49% free, 2MB/4MB, paused 1.230ms total 35.789ms

这一行信息量很大。我来拆解一下:

  • Explicit concurrent:显式触发的并发 GC。说白了就是有人调用了 System.gc()。我在项目中遇到过,某些第三方 SDK 特别喜欢干这事儿,结果导致界面掉帧。
  • freed 2047(64KB):回收了 2047 个对象,释放了 64KB 内存。嗯,这里要注意,如果释放量很小,说明内存管理还不错。
  • 49% free, 2MB/4MB:堆内存使用情况。49% 空闲,总共 4MB,用了 2MB。这个比例如果低于 10%,说明内存快爆了。
  • paused 1.230ms total 35.789ms:暂停时间 1.23 毫秒,总耗时 35.789 毫秒。暂停时间越短越好,超过 5ms 就可能掉帧。

关键指标:重点关注 paused 时间和 % free。如果 paused 经常超过 3ms,你的 UI 线程大概率在受苦。

我曾经接手过一个项目,首页列表滑动卡成 PPT。打开 Logcat 一看,GC 日志每两秒就刷一次,paused 时间平均 8ms。嗯,找到根儿了——内存泄漏导致 GC 频繁触发。

15.2 CPU 负载:别让系统「满负荷」

CPU 负载高,手机发烫,电量狂掉。怎么用 Logcat 看?

过滤关键字 CPU 或者 Load。系统会定期输出 CPU 使用情况:

I/ActivityManager: CPU: 12% user, 8% kernel, 80% idle

这个输出告诉你三件事:

  • user:用户态 CPU 占用。你的 App 代码跑在这里。
  • kernel:内核态 CPU 占用。系统调用、驱动等。
  • idle:空闲率。低于 30% 就要警惕了。

你想想看,如果 idle 只有 10%,说明 CPU 快被榨干了。这时候任何操作都会卡。

我的小技巧:在 Logcat 里加一个过滤器 package:com.your.app + CPU,只看自己进程的 CPU 负载。这样能排除其他 App 的干扰。

还有一种情况,某个线程在死循环。你会看到 CPU 负载突然飙到 90% 以上,而且持续不降。这时候用 ps -T 配合 Logcat 看线程名,能快速定位问题线程。

15.3 内存抖动:看不见的「锯齿」

内存抖动,英文叫 Memory Churn。说白了就是内存频繁分配和释放,导致 GC 不断触发。

怎么在 Logcat 里识别?看 GC 日志的频率。如果每秒钟出现多次 GC 日志,那就是抖动。

举个例子,你写了个循环:

for (i in 0..1000) {
    val str = "Item $i"  // 每次循环都创建新字符串
    list.add(str)
}

这段代码在循环里频繁创建对象。如果循环次数多,GC 就会被反复触发。Logcat 里会看到:

I/art: Background partial concurrent mark sweep GC freed 1000 objects
I/art: Background partial concurrent mark sweep GC freed 1000 objects
I/art: Background partial concurrent mark sweep GC freed 1000 objects

嗯,这就是典型的抖动。我见过最夸张的一次,某个页面在 5 秒内触发了 30 次 GC,界面直接卡死。

避坑指南:我曾经在列表的 onBindViewHolder 里直接拼接字符串,导致每滚动一屏就触发多次 GC。后来改用 StringBuilder,GC 频率直接降了 80%。

15.4 知识体系总览

下面这张图,是我自己总结的 Logcat 性能分析框架。你可以把它当成一个检查清单:

Logcat 性能分析三要素 GC 日志 • 暂停时间 (paused) • 空闲比例 (% free) • 回收对象数 • GC 触发频率 常见问题: • 频繁 GC → 内存抖动 • 暂停时间长 → 掉帧 • 空闲比例低 → 内存泄漏 CPU 负载 • user 用户态占比 • kernel 内核态占比 • idle 空闲率 • 线程 CPU 时间 常见问题: • idle < 30% → 过载 • 单线程 100% → 死循环 • 频繁切换 → 性能差 内存抖动 • GC 触发频率 • 对象分配速率 • 堆内存增长曲线 • 临时对象数量 常见问题: • 循环内创建对象 • 频繁字符串拼接 • 匿名内部类泄漏 三者相互影响:GC 频繁 → CPU 负载高 → 内存抖动加剧

15.5 实战:一次完整的性能排查

我记得有一次,用户反馈某个页面「点不动」。我打开 Logcat,做了三步:

  1. 看 GC 频率:过滤 GC,发现每 500ms 就触发一次。不正常。
  2. 看 CPU 负载:过滤 CPU,发现 idle 只有 15%。CPU 被吃满了。
  3. 定位代码:用 ps -T 找到 CPU 占用最高的线程,发现是 RenderThread 在疯狂重绘。

最终原因是什么?一个自定义 View 在 onDraw 里 new 了 Paint 对象。每次绘制都创建新对象,导致 GC 不断触发,CPU 忙于回收,UI 线程被频繁暂停。

我的建议:把 Logcat 当成你的「听诊器」。性能问题出现时,先听 GC 的「心跳」,再看 CPU 的「呼吸」,最后查内存的「脉搏」。三步走,基本能定位 90% 的问题。

好了,这一章的内容就到这里。Logcat 在性能分析上的能力远不止这些,但掌握了 GC、CPU 和内存抖动这三个维度,你已经能解决大部分日常问题了。


公众号:蓝海资料掘金营,微信deep3321