JavaScript高级深入浅出:JS 中的内存管理

526 阅读3分钟

介绍

本文是 JavaScript 高级深入浅出系列的第四篇,详细介绍了在 JS 中的内存管理

正文

1. 认识内存管理

任何编程语言,在代码执行过程中都需要分配给其内存,这样 CPU 才可以执行。不同的是某些编程语言需要自己手动管理内存,有些则是自动管理内存。

内存的管理一般有以下的生命周期:

  • 申请内存空间
  • 使用分配的内存
  • 释放内存

不同的编程语言有着不同内存管理的实现:

  • 手动管理内存:C、C++等,需要手动管理内存的申请和释放
  • 自动管理内存:Java、JS、Python等

一般情况下 JS 不需要自动手动管理申请和释放内存(比如闭包就需要手动释放内存)

2. JS 的内存管理

JS 在定义变量时会为我们分配内存

但是内存分配的方式是不一样的:

  • 对于基本数据类型:会在执行时,在栈空间进行分配
  • 对于复杂数据类型:会在堆内存中开辟空间,并将这块空间的指针返回给变量来引用

3. JS 的垃圾回收

因为内存的大小是有限的,因此当内存不再需要时,需要释放内存,以便能腾出更多的内存空间

在手动管理内存的语言中,我们需要通过一些方式来手动释放内存

  • 但是这种管理的方式非常低效,影响编码的效率
  • 这种方式也对开发者有一定的技术要求,容易造成内存泄漏

所以大部分的现代变成语言都会有自己的垃圾回收机制:

  • 垃圾回收(Garbage Collection, GC)
  • 对于不再使用的变量、函数,我们称之为垃圾,需要被收回释放内存空间
  • Java 的 JVM,JS 的 JS 引擎都有垃圾回收器
  • 垃圾回收器也成为 GC,我们看到的 GC 一般都代指垃圾回收器
  • 由 GC 算法来判断哪些对象不再被使用

4. 常见的 GC 算法

4.1 引用计数

let foo = { name: 'alex' }
let bar = { age:18, sub: foo }
let baz = { age:20, sub: foo }

foo被引用了3次(bar、baz以及foo本身),那么内部的引用计数器就计算出了有3次引用,一旦引用次数到0之后,就开始触发垃圾回收机制。

但是引用计数有很大的弊端:循环引用,如果没有指定一个的 sub 是 null,那么他们永远不会触发垃圾回收机制,就会造成内存泄漏

let foo = { sub: bar }
let bar = { sub: foo }

4.2 标记清除

这个算法是设置了一个根对象(root object),垃圾回收器会定期从这个根开始,找所有从根开始有引用的对象,对于哪些没有引用的对象,判断就是没用的对象。

这个算法可以解决循环引用的问题:

标记清除

虽然 Z 和 V 相互引用,但并未和根节点 A 产生任何关系,所以就会触发垃圾回收机制回收

JS 引擎比较广泛的是采用标记清除的方案,当然 V8 也做了很多优化

总结

这篇文章,你学到了 3 个知识点

什么是内存管理以及 JS 的内存管理

为了更好的利用计算机资源,程序在运行时需要及时释放内存,这样才不会导致内存泄漏。一些编程语言需要自己手动管理内存,对于开发效率很不友好。JS 可以自动管理内存,主要依赖于内部的垃圾回收机制(GC)

两种常见的 GC 算法

引用计数,主要是依靠于内部的引用计数器,有循环引用这个弊端

而标记清楚则是通过一个根对象中去追踪依赖,很好的解决了循环引用的问题