面试题之垃圾回收

简介: 机制 标记清除 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 内存泄露 - 格西南
相关文章
|
4月前
|
存储 算法 安全
JVM常见面试题(四):垃圾回收
堆区域划分,对象什么时候可以被垃圾器回收,如何定位垃圾——引用计数法、可达性分析算法,JVM垃圾回收算法——标记清除算法、标记整理算法、复制算法、分代回收算法;JVM垃圾回收器——串行、并行、CMS垃圾回收器、G1垃圾回收器;强引用、软引用、弱引用、虚引用
|
5月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
8月前
|
存储 缓存 监控
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
102 0
|
8月前
|
缓存 监控 算法
Java面试题:描述Java垃圾回收的基本原理,以及如何通过代码优化来协助垃圾回收器的工作
Java面试题:描述Java垃圾回收的基本原理,以及如何通过代码优化来协助垃圾回收器的工作
113 8
|
8月前
|
监控 算法 Java
Java面试题:如何在Java中触发一次Full GC?请详细解释垃圾回收机制和知识
Java面试题:如何在Java中触发一次Full GC?请详细解释垃圾回收机制和知识
502 4
|
8月前
|
算法 Java
Java面试题:解释垃圾回收中的标记-清除、复制、标记-压缩算法的工作原理
Java面试题:解释垃圾回收中的标记-清除、复制、标记-压缩算法的工作原理
101 1
|
8月前
|
存储 监控 算法
Java面试题:解释分代垃圾回收策略,并说明其优势
Java面试题:解释分代垃圾回收策略,并说明其优势
77 0
|
8月前
|
算法 Java 程序员
Java面试题:解释Java的垃圾回收机制,包括常见的垃圾回收算法。介绍一下Java的垃圾回收算法中的标记-压缩算法。
Java面试题:解释Java的垃圾回收机制,包括常见的垃圾回收算法。介绍一下Java的垃圾回收算法中的标记-压缩算法。
69 0
|
3月前
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
5月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
215 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS

热门文章

最新文章