Retained Size
:与Shallow Size
不同,这个数字代表该类所有实例及其所有引用到的对象的内存占用大小;
借助一张图,可以对这几个属性有更直观的印象:
如上图,红点的内存大小代表 Shallow Size
,蓝点为 Native Size
,所有橙色点的内存大小则为 Retained Size
;当出现内存泄漏时,我们更应该关注 Retained Size
这个数字,它的意义是,因内存泄漏导致 Java 堆内存中所浪费的内存空间大小。 因为内存泄漏往往会形成“链式效应”,从泄漏的对象出发,该对象引用的所有对象和 Native 资源都无法回收,造成内存使用效率的下降。
另外 Leaks
代表可能的内存泄漏实例数量;点击列表中的类可以查看该类的实例详情;Instance 列表中的 depth
代表该实例到达 GC Root
的最短调用链深度,在图1右侧 Reference
一栏堆栈中可以直观地看到完整调用链,这时就可以一路追溯找出最可疑的引用,结合代码分析泄漏原因,并对症下药,根治问题。
接下来分析几个我们在项目中遇到一部分典型内存泄漏的案例:
案例剖析
案例1:BitmapBinder 内存泄漏
在涉及跨进程传输 Bitmap 的场景时,我们采用了一种 BitmapBinder
的方法;因为 Intent 支持我们传入自定义的 Binder,因此可以借助 Binder 实现 Intent 传输 Bitmap 对象:
// IBitmapBinder AIDL文件
import android.graphics.Bitmap;
interface IBitmapInterface {
Bitmap getIntentBitmap();
}
然而,Activity1
在使用 BitmapBinder
向 Activity2
传递 Bitmap 后,出现了两个严重的内存泄漏问题:
- 跳转后再返回,
Activity1
finish 时无法回收; - 反复跳转时,
Bitmap
和Binder
对象会反复创建且无法回收;
先分析 Heap Dump:
这是一个『多实例』内存泄漏,即每次 finish Activity1
再打开,都会增加一个 Activity 对象留在 Heap 中,无法销毁;常见于内部类引用、静态数组引用(如监听器列表)等场景;根据 Profiler 提供的引用链,我们找到了 BitmapExt
这个类:
suspend fun Activity.startActivity2WithBitmap() {
val screenShotBitmap = withContext(Dispatchers.IO) {
SDKDeviceHelper.screenShot()
} ?: return
startActivity(Intent().apply {
val bundle = Bundle()
bundle.putBinder(KEY_SCREENSHOT_BINDER, object : IBitmapInterface.Stub() {