面试题之垃圾回收

简介: 机制 标记清除 JS 中最常见的垃圾回收方式是标记清除 标记清除的概念也好理解,从根部出发看是否能达到某个对象,如果能达到则认定这个对象还被需要,如果无法达到,则释放它,这个过程大致分为三步:

机制

标记清除

JS 中最常见的垃圾回收方式是标记清除\
标记清除的概念也好理解,从根部出发看是否能达到某个对象,如果能达到则认定这个对象还被需要,如果无法达到,则释放它,这个过程大致分为三步:

  • 垃圾回收器创建 roots 列表,roots 通常是代码中保留引用的全局变量(因此引起内存泄漏原因之一会有全局变量),在 js 中,我们一般认定全局对象 window 作为 root,也就是所谓的根部。
  • 从根部出发检查所有的 roots,所有的 children 也会被递归检查,能从 root 到达的都会被标记为active
  • 未被标记为 active 的数据被认定为不再需要,垃圾回收器开始释放它们。

怎么理解?想象一下你自己去实现,假设你有一个包含所有对象的数组,假设为 a需要清除不需要清除的对象,找到 roots 列表,然后做一个 dfs 遍历标记找到的对象,然后在对 a 数组进行遍历,没有被标记的就清楚

再来一个垃圾回收实例

根对象

  1. 全局对象,window
  2. 浏览器对象,如 DOM 元素

变量回收原则

  1. 全局变量不会被回收
  2. 局部变量会被回收,也就是函数一旦运行完以后,函数内部的东西都会被销毁
  3. 只要被另外一个作用域所引用就不会被回收

内存泄漏

即无法释放已经不使用的内存,有如下情况

全局变量

意外的全局变量引起的内存泄漏,如

// 没有使用 let/const/var 声明的变量会自动变成全局变量
function foo(arg) {
  bar = "this is a hidden global variable";
}
// foo() 执行环境为全局,this 指向 window 对象
function foo() {
  this.bar = "this is a hidden global variable";
}
foo();

\
原因:全局变量,不会被回收。\
解决:使用严格模式避免。

闭包

闭包引起的内存泄漏,如

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing) console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join("*"),
    someMethod: function () {
      console.log(someMessage);
    },
  };
};
setInterval(replaceThing, 1000);

\
原因:闭包可以维持函数内局部变量,使其得不到释放。\
解决:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对 dom 的引用。

被遗忘的定时器或者回调

如下

<script>
export default {
  mounted() {
    this.resizeEventCallback = () => {
      // 这里做一些操作
    }
    window.addEventListener('resize', this.resizeEventCallback)
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.resizeEventCallback)
  },
}
</script>

原因:定时器中有 dom 的引用,即使 dom 删除了,但是定时器还在,所以内存中还是有这个dom。\
解决:手动删除定时器和 dom

  1. 没有清理的DOM元素引用\
    原因:虽然别的地方删除了,但是对象中还存在对dom的引用\
    解决:手动删除。
  2. 子元素存在引用引起的内存泄漏\
    原因:div中的ul li 得到这个div,会间接引用某个得到的li,那么此时因为div间接引用li,即使li被清空,也还是在内存中,并且只要li不被删除,他的父元素都不会被删除。\
    解决:手动删除清空。

避免内存泄漏的方法

  1. 少用全局变量,避免意外产生全局变量
  2. 使用闭包要及时注意,有Dom元素的引用要及时清理。
  3. 计时器里的回调没用的时候要记得销毁。
  4. 为了避免疏忽导致的遗忘,我们可以使用 WeakSetWeakMap 结构,它们对于值的引用都是不计入垃圾回收机制的,表示这是弱引用。

示例

var i = 1;
var i = 2;
var add = (function () {
  var i = 0;
  return function () {
    i++;
    console.log(i);
  };
})();
add();

answer: 3,全局变量有两个,即 iadd,局部变量有一个 i,因为局部变量被另一个作用域引用,所以局部变量 i 也不回收

var i = 1; // 全局变量不会被回收
var i = 2; // 这里重复声明变量i,因此var声明被忽略,只是把i赋值为2
var add = (function () {
  // 全局变量不会被回收
  var i = 0; // 局部变量
  return function () {
    i++;
    console.log(i); // 被另一个作用域引用导致不会被回收
  };
})();
add();

参考资料

  1. JavaScript变量回收原则/垃圾回收机制
  2. Chorme 浏览器中的垃圾回收和内存泄漏
  3. 详细讲解node/v8/js垃圾回收机制 - Alan Wei
  4. 深入了解 JavaScript 内存泄露 - 格西南
相关文章
|
19天前
|
SQL 缓存 安全
大厂面试题:垃圾回收与String
大厂面试题:垃圾回收与String
21 2
|
11月前
|
存储 算法 安全
15-大厂面试题-JVM垃圾回收采用的是什么算法,有什么区别和优劣?
通过之前的学习,我们知道了JVM会通过**可达性算法**来筛选出哪些对象是可回收的,哪些对象是不可回收的,GCRoots对象是哪些,java的引用类型有哪些以及finlize()方法的作用。同时我们也知道了当一个对象在创建的时候是存放在堆内存中的新生代里的,那么当新生代内存满了后就会触发Minor GC;但是问题是我们如何针对新生代内存进行管理,以及如何进行回收这也是一个值得分析和探讨的问题。
73 0
|
11月前
|
存储 缓存 算法
13-大厂面试题:为什么要垃圾回收以及如何判断对象可以回收
接下来我们正式进入第二个系列,关于垃圾回收以及优化。
85 0
13-大厂面试题:为什么要垃圾回收以及如何判断对象可以回收
|
10月前
|
算法 Java 大数据
第二季:6.GC垃圾回收算法和垃圾收集器的关系?分别是什么请你谈谈【Java面试题】
第二季:6.GC垃圾回收算法和垃圾收集器的关系?分别是什么请你谈谈【Java面试题】
69 0
|
10月前
|
算法 Java
第二季:1.JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots【Java面试题】
第二季:1.JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots【Java面试题】
80 0
|
19天前
|
算法 Java
JVM GC和常见垃圾回收算法
JVM GC和常见垃圾回收算法
54 0
|
19天前
|
Java Go
Golang底层原理剖析之垃圾回收GC(二)
Golang底层原理剖析之垃圾回收GC(二)
54 0
|
19天前
|
存储 缓存 算法
JVM(四):GC垃圾回收算法
JVM(四):GC垃圾回收算法
|
19天前
|
存储 监控 算法
垃圾回收器、垃圾回收算法、空间分配担保、JVM调优、GC回收对象的过程
垃圾回收器、垃圾回收算法、空间分配担保、JVM调优、GC回收对象的过程
|
10天前
|
存储 算法 Java
JVM(垃圾回收机制 --- GC)
JVM(垃圾回收机制 --- GC)
31 5