Hummer 引擎优化系列 - 揭秘最强内存泄漏检测工具

重点需要确定什么时候 Element 会被 Element Tree 丢弃,并且不会再使用,会被随后来的 GC 回收掉。

finalizeTree 处理代码如下:

// flutter_sdk/packages/flutter/lib/src/rendering/binding.dart

mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {

@override

void drawFrame() {

try {

if (renderViewElement != null)

buildOwner.buildScope(renderViewElement);

super.drawFrame();

// 每一帧最后回收从 Element 树中移除的 Element

buildOwner.finalizeTree();

} finally {

}

}

}

// flutter_sdk/packages/flutter/lib/src/widgets/framework.dart

class BuildOwner {

void finalizeTree() {

try {

// _inactiveElements 中记录不再使用的 Element

lockState(() {

_inactiveElements._unmountAll(); // this unregisters the GlobalKeys

});

} catch() {

}

}

}

// flutter_sdk/packages/flutter/lib/src/widgets/framework.dart

class _InactiveElements {

void _unmountAll() {

_locked = true;

// 将 Element 拷贝到临时变量 elements 中

final List elements = _elements.toList()…sort(Element._sort);

// 清空 _elements,当前方法执行完,elements 也会被回收,则全部 Element 正常情况下都会被 GC 回收。

_elements.clear();

try {

elements.reversed.forEach(_unmount);

} finally {

assert(_elements.isEmpty);

_locked = false;

}

}

}

复制代码

finalize 阶段 _inactiveElements 中保存了被 Element Tree 丢弃,并且不会再使用的 Element;在执行完 unmount 方法后,即等待被 GC 回收。

因此 Element 泄漏可定义为:执行完 umount,并且 GC 后,仍存在这些 Element 的引用,则说明 Element 发生内存泄漏。满足条件 2。

内存泄漏检测工具


工具描述

我们对内存泄漏工具有 2 点要求:

  1. 准确。包括核心对象泄漏检测:image, layer,state,能够解决 Flutter 90% 以上对内存泄漏问题

  2. 高效。业务无感,自动化检测,优化引用链,快速定位到泄漏源

准确

从上文描述,BuildContext 毫无疑问是最有可能导致大内存泄漏的对象,是作为监控对象的最佳对象。为了提高准确度,我们也把最常用的 State 对象监控起来。

为什么要添加 State 对象的监控呢?

因为业务逻辑控制实现在 State 中,业务中实现的“闭包或者方法”传递很容易导致 State 泄漏。例子如下。

class MainApp extends StatefulWidget {

@override

State createState() {

return _MainAppState();

}

}

class _MainAppState extends State {

@override

void initState() {

super.initState();

// 注册这个回调,这个回调如果没有被反注册或者被其他上下文持有,都会导致 _MainAppState 泄漏。

xxxxManager.addListerner(handleAction);

}

@override

Widget build(BuildContext context) {

return MaterialApp(

);

}

// 1个回调

void handleAction() {

}

}

复制代码

State 关联哪些内存会被泄漏?

结合以下代码看,泄漏肯定会导致关联的 Widget 泄漏,而 Widget 关联的内存如果是一张的 Image 或者 gif 的话,泄漏的内存也会很大。同时,State 中可能还以关联其他的一些强引用住的内存。

// flutter_sdk/packages/flutter/lib/src/widgets/framework.dart

abstract class State with Diagnosticable {

// 强引用对应的 Widget 泄漏

T _widget;

// unmount 时候,_element = null, 不会导致泄漏

StatefulElement _element;

}

// flutter_sdk/packages/flutter/lib/src/widgets/framework.dart

class StatefulElement extends ComponentElement {

@override

void unmount() {

_state.dispose();

_state._element = null;

// 其他地方持有,则导致泄漏。unmount 后 State 仍被持有,可作为一个泄漏定义。

_state = null;

}

}

复制代码

所以,我们方案将关联大内存的 BuildContext,业务常操作的 State 一并监控起来,提高整套方案的准确度。

高效

怎么实现自动化高效的内存泄漏检测?

首先我们要怎么明确一个对象是否发生泄漏?以 BuildContext 为例,我们采取类似“Java 对象弱引用”判定对象泄漏的方式:

  1. 将 finalizeTree 阶段的 inactiveElements 放到 weak Reference map 中

  2. Full GC 后检测 weak Reference map ,如果其中仍持有未释放的 Element,则判定为发生泄漏

  3. 将泄漏的 Element 关联的 size,对应的 Widget,泄漏引用链信息输出

虽然 Dart 没有直接提供“弱引用”检测能力,但我们 Hummer 引擎从底层将“弱引用泄漏检测”功能完整实现了,这里简单介绍它判定泄漏的接口:

// 添加需要检测泄漏的对象,类似将对象放到若引用map中

external void leakAdd(Object suspect, {

String tag: ‘’,

});

// 检测之前放入的对象是否发生了泄漏,会进行 FullGc

external void leakCheck({

Object? callback,

String tag: ‘’,

bool clear: true,

});

external void leakClear({

String tag: ‘’,

});

external String leakCount();

external List leakTags();

复制代码

因此,要实现自动化检测,我们只需要明确 leakAdd(),leakCheck() 调用的时机即可。

leakAdd 时机

BuildContext 的时机在 finalizeTree 的 unmount 流程中:

// flutter_sdk/packages/flutter/lib/src/widgets/framework.dart

class _InactiveElements {

void _unmount(Element element) {

element.visitChildren((Element child) {

assert(child._parent == element);

_unmount(child);

});

// BuildContext 泄漏 leakAdd() 时机

if (!kReleaseMode && debugMemoryLeakCheckEnabled && null != debugLeakAddCallback) {

debugLeakAddCallback(_state);

}

element.unmount();

}

}

复制代码

State 的时机在对应的 StatefulElement 的 unmount 流程中:

// flutter_sdk/packages/flutter/lib/src/widgets/framework.dart

class StatefulElement extends ComponentElement {

@override

void unmount() {

_state.dispose();

_state._element = null;

// State 泄漏 leakAdd() 时机

if (!kReleaseMode && debugMemoryLeakCheckEnabled && null != debugLeakAddCallback) {

debugLeakAddCallback(_state);

}

_state = null;

}

}

复制代码

leakCheck 时机

leakCheck 本质上是一个检测是否存在泄漏的时机点,我们认为 Page 退出是个合适的时机,以业务 Page 为单位进行内存泄漏检测。示例代码如下:

// flutter_sdk/packages/flutter/lib/src/widgets/navigator.dart

abstract class Route {

_navigator = null;

// BuilContext, State leakCheck时机

if (!kReleaseMode && debugMemoryLeakCheckEnabled && null != debugLeakCheckCallback) {

debugLeakCheckCallback();

}

}

复制代码

工具实现

以 Page 为单位的自动化内存泄漏,根据使用场景,提供三种内存泄漏检测工具。

  1. Hummer 引擎深度定制的 DevTools 资源面板展示,可以自动/手动触发内存泄漏检测

  2. 独立 APP 端内存泄漏展示,在 Page 发生泄漏时候,弹出泄漏对象详情

  3. Hummer 引擎海鸥实验室自动化检测,自动化将内存泄漏详情以报告给出

工具 1、2 提供开发过程的内存泄漏检测能力,工具 3 可作为 APP 常规健康测试,自动化测试并输出检测报告结果。

异常检测实例

在 Demo 中模拟 StatelessWidget, StatefulWidget 被 BuildContext 持有导致的泄漏。泄漏的原因是被静态持有,Timer 异常持有。

// 验证 StatelessWidget 泄漏

class StatelessImageWidget extends StatelessWidget {

@override

Widget build(BuildContext context) {

// 模拟静态持有 BuildContext 导致泄漏

MyApp.sBuildContext.add(context);

return Center(

child: Image(

image: NetworkImage(“https://avatars2.githubusercontent.com/u/20411648?s=460&v=4”),

width: 200.0,

)

);

}

}

class StatefulImageWidget extends StatefulWidget {

@override

State createState() {

return _StatefulImageWidgetState();

}

}

// 验证 StatefulWidget 泄漏

class _StatefulImageWidgetState extends State {

@override

Widget build(BuildContext context) {

if (context is ComponentElement) {

print(“sBuildContext add :” + context.widget.toString());

}

// 模拟被 Timer 异步持有 BuildContext 导致泄漏,延时 1h 用于说明问题

Timer(Duration(seconds: 60 * 60), () {

print(“zw context:” + context.toString());

});

return Center(

child: Image(

image: NetworkImage(“https://avatars2.githubusercontent.com/u/20411648?s=460&v=4”),

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

推荐学习资料


  • 脑图
    360°全方位性能调优

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

52277)]

[外链图片转存中…(img-eGQKES7S-1713378752278)]

[外链图片转存中…(img-Zz7cnGCT-1713378752279)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

推荐学习资料


  • 脑图
    [外链图片转存中…(img-EVNGPDBZ-1713378752280)]
    [外链图片转存中…(img-yOF4sa4R-1713378752281)]
    [外链图片转存中…(img-gWZ6pmpq-1713378752282)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值