GC垃圾回收机制和内存优化实例

41 阅读4分钟

1.GC垃圾回收机制

1.1 垃圾的相关概念
  • 对象不再被引用的时候就是垃圾;
  • 对象不能从根上访问到的时候也是垃圾(也称为不是可达对象);
2.1 GC算法介绍
  1. 引用计数: 某个对象被其他变量引用一次,引用计数+1,赋值一次null,则引用计数-1,为0时被清除。问题:a.to = b; b.to = a; 说明:a/b各为1,无法被清除;
  2. 标记清除:第一轮扫描a、b,标记活动对象,因其不为活动对象(即你指向我、我指向你),故不标记。第二轮扫描没有标记的对象进行清除;
  3. 标记整理:同【标记清除】 清除之前会先执行整理,移动对象位置,使得他们再地址上是一个连续的空间;

image.png

2.内存优化实例

2.1 window造成泄漏
function Test() { }
// 大一点的数组 里面装这个类型 //可以执行map
(function () {
  let arr = new Array(200000).fill(null).map(e => new Test())
  // 不小心将数据挂载到了window上,再看看内存
  window.arr2 = arr;
  window.arr3 = arr;
  window.arr4 = arr;
})();

此时查看内存

image.png

进行内存优化

function Test() { }
// 大一点的数组 里面装这个类型 //可以执行map
(function () {
  let arr = new Array(200000).fill(null).map(e => new Test())
  // 不小心将数据挂载到了window上,再看看内存
  window.arr2 = arr;
  window.arr3 = arr;
  window.arr4 = arr;
})();

// 不确定引用次数时,访问数组.length属性,直接实现数组长度清零
window.arr2.length = 0;
// null 释放内存(这样只能清除arr2,arr3和arr4还在,多次被引用时释放的不彻底)
window.arr2 = null;

手动释放内存后查看内存

image.png

2.2 闭包造成泄漏
function Test() { }
function closure() {
  var arr = new Array(200000).fill(null).map(e => new Test())
  return function inner() {
    return arr; // 一个函数 内部访问外部变量 形成闭包
  }
}

此时查看内存

image.png

function Test() { }
function closure() {
  var arr = new Array(200000).fill(null).map(e => new Test())
  return function inner() {
    return arr; // 一个函数 内部访问外部变量 形成闭包
  }
}

// 函数由于被外部持有, 自身不会释放, 同时闭包也不会释放: 访问外部变量
let fn = closure();
// 巴拉巴拉使用完fn之后
fn = null; // 进行内存释放

释放内存后查看内存

image.png

2.3 已分离DOM造成泄露

已分离的DOM: 当页面DOM被JS所引用时,产生的一个指向,随着页面DOM被删除, 该指向还没有赋值为null,就会形成一个已分离的DOM的指向关系(占据JS内存

let f = document.querySelector('#f');
let z = document.querySelector('#z');
// 引用页面元素时, 仅仅删除页面元素, 不能释放js引用
setTimeout(() => {
  // f.removeChild(z);
  document.body.removeChild(f);
}, 1000);

查看内存

image.png

let f = document.querySelector('#f');
let z = document.querySelector('#z');
// 引用页面元素时, 仅仅删除页面元素, 不能释放js引用
setTimeout(() => {
  // f.removeChild(z);
  document.body.removeChild(f);
  // 删除已分离DOM的引用
  f = z = null;
}, 1000);

进行f = z = null,释放内存后再次查看内存

image.png

2.4 console内存泄露
function Test() { }
// 引用+1
var arr = new Array(200000).fill(null).map(e => new Test());
// console.log(arr); // 引用+1

查看内存

image.png

  • 此时使用arr = null; // 引用-1 数据还是存在的
  • 生产环境我们都会用对应的包来移除console相关的代码,所以记得移除就不必太在意了。推荐uglifyjs-webpack-plugin
2.5 事件监听造成泄露
let fns = [];
new Array(10000).fill(null).forEach(e => {
  function tmp() { }
  fns.push(tmp); // 保存为了后续方便移除
  window.addEventListener('keypress', tmp)
});ndow.removeEventListener('keypress', fn);
})

查看内存

image.png 使用.removeEventListener()函数移除事件

let fns = [];
new Array(10000).fill(null).forEach(e => {
  function tmp() { }
  fns.push(tmp); // 保存为了后续方便移除
  window.addEventListener('keypress', tmp)
});
fns.forEach(fn => {
  window.removeEventListener('keypress', fn);
})

此时查看内存

image.png

2.6 集合造成泄露
function Test() { }
var wm = new Map(); // 强引用,引用计数会加一
var b = new Object();
wm.set(b, new Array(200000).fill(null).map(e => new Test()));  // 开辟内存
b = null;

使用WeakMap对数据弱引用,实现垃圾回收

function Test() { }
var wm = new WeakMap(); // 对数据弱引用 不会给引用计数+1
var b = new Object();
wm.set(b, new Array(200000).fill(null).map(e => new Test()));  // 开辟内存
b = null;

Map和WeakMap的区别就在于WeakMap是虚引用,key清空后,value的内存会释放