介绍
本文是 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 算法
引用计数,主要是依靠于内部的引用计数器,有循环引用这个弊端
而标记清楚则是通过一个根对象中去追踪依赖,很好的解决了循环引用的问题