lihuakkk/blog

前端内存泄漏分析以及在AngularJs中的实践

Opened this issue · 0 comments

内存泄漏分析

对于hybrid App来说,应用的内存管理是个很重要的一环,如果存在问题,很容易导致应用卡顿或崩溃。

先说结论

short live objectlong live object 交互的时候(通常是以回调函数的形式),最容易因为开发者的疏忽产生内存泄漏问题。

内存相关的知识

内存在应用中存在的形式可以抽象为具有原始类型(如数字和字符串)和对象(关联数组)的图表,可以表示为一个由多个互连的点组成的图表,如下图所示:
image

内存图从根开始,根可以是浏览器的 window 对象或 Node.js 中的 Global 对象。

对象如何占用内存?

对象通过以两种方式占用内存:

  1. 直接通过对象自身占用。
  2. 通过保持对其他对象的引用隐式占用,这种方式可以阻止这些对象被垃圾回收器(简称 GC)自动处置。所以即使一个小对象也可能通过阻止其他对象被自动垃圾回收进程处理的方式间接地占用大量内存。
那些对象会被回收?

任何无法从根到达的对象都会被 GC 回收。上图中,9,10节点占用的内存将会被回收。

作用域链和闭包

当一个函数被创建的时候,跟这个函数一起被创建的是这个函数的作用域链。作用域链里面包括所有这个函数可以被访问到的数据集合。

当一个闭包函数被创建的时候,这个闭包函数的作用域链也被创建,该作用域链对宿主函数的作用域链是存在引用关系的,所以闭包函数里面可以访问宿主函数的变量。

如果闭包函数如果被宿主函数外的变量引用,这个变量会对宿主函数的作用域链存在一个间接引用。所以这种情况下,即使宿主函数执行结束,它的作用域链里面的变量也不会被回收。要解决这个问题,就要切断闭包函数的引用,通常是将引用闭包函数的变量置为null。

AngularJs中的实践

这是一段在Controller里面的代码:

function Ctrl($interval, $scope) {
  $interval(function() {
      $scope.m = 'mm';
  }, 10 * 1000)
}

$interval的第一个参数是一个回调函数同时也形成了一个闭包,这个闭包保存着对$scope的引用。如果$scope destroy的时候$interval服务未执行cancel方法,闭包函数会对$scope一直保持着引用关系,这时会发生内存泄漏问题。

解决方法:

$scope.$on('$destroy', function() {
  $interval.cancel(intervalId);
})

在看另一段在Controller里面的代码:

UserService.onNameChange(function(newName) {
  $scope.userName = newName;
});

这里使用一个UserService服务用来更新用户信息,同样回调函数形成了一个闭包,跟上面的原因类似,这个闭包对$scope存在引用,如果不断开引用,会阻止系统对$scope及其相关联对象的内存回收。

解决方法:使用$broadcasts,或者使用合适的方式解决内存隐患问题(比如将函数引用设为null)。

总结下上面的例子,编程时应该:

  1. 充分理解框架及其生命周期
  2. short live objectlong live object通过回调函数交互时留意。

参考资料:
内存术语
JavaScript 核心概念之作用域和闭包
Fixing Memory Leaks in AngularJS and other JavaScript Applications