Catalog
  1. 1. 前端为什么要关注内存?
  2. 2. JS数据类型与JS内存机制
  3. 3. 垃圾回收
    1. 3.1. 引用计数
    2. 3.2. 标记清除
  4. 4. V8内存管理机制
javascript内存管理

前端为什么要关注内存?

首先我们分析一下,作为一个前端程序员为什么要关注内存。

任何程序的运行都需要分配内存空间,对于一个页面来说如果一些不再需要使用的内存没有得到及时的释放,这种情况就叫做内存泄漏。一次内存泄漏似乎不会产生很大的影响,但内存泄漏堆积会造成内存溢出。

内存溢出简单来说就是我们所需要使用的内存空间大于可用内存,此时我们的程序就会出现内存溢出错误。这就好比我们生活中一直往水桶里注水却不倒水,最终会导致水溢出一样,水桶的容量有限,我们的内存空间也是有限的,我们网页端的承载量也是有限的。在浏览器的表现为长时间无响应或者崩溃。

所以作为一个前端程序员,为了给我们的用户带来良好的用户体验,我们要注意如下几点:

  • 防止页面占用内存过大,引起客户端卡顿,甚至无响应。
  • Nodejs使用V8引擎,内存对于后端服务的性能至关重要,因为后端服务的持久性,后端程序更容易造成内存溢出。

JS数据类型与JS内存机制

JS中数据类型分为原始数据类型和引用数据类型:

  • 原始数据类型:字符串(String)、数字(Number)、布尔(Boolean)、空对象(Null)、未定义(Undefined)、Symbol(ES6 引入了一种新的原始数据类型,表示独一无二的值)
  • 引用数据类型:对象(Object)、数组(Array)、函数(Function)

在JavaScript中每一个数据都需要一个内存空间,内存空间分为两种:栈内存(stack)、堆内存(heap)。
原始数据类型都有固定大小,保存在栈内存中,由系统自动分配存储空间,我们可以直接进行操作,因此原始数据类型都是按值访问,所以也叫做值类型。

图示

栈是限定仅在表尾进行插入和删除操作的线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。

如上图所示,当我们定义一个变量时,即为入栈,比如a,我们先将10存入内存空间,然后在我们的当前作用域中声明一个变量a,此时只是声明了变量a并没有赋值,所以a的值为undefined,当声明完了之后才会对a进行赋值,而赋值的过程只是将变量a与10进行一个关联,当a和10关联完成才算定义变量结束。a此时为栈顶元素,当b定义完b就为新的栈顶元素,依此类推。当删除一个变量,即为出栈,会先删除栈顶元素c,再是b,最后才是a。

引用数据类型的大小是不固定的,保存在堆内存中。js不允许直接访问堆内存,因此也无法直接操作对象的堆内存空间。我们在操作对象时,实际上是在操作对象的引用,而不是实际的对象,因此引用类型的值都是按引用访问的。

图示

如上图所示,我们理解为保存在栈内存中的内存地址,该地址与堆内存中的实际值相关联,所以引用类型的值是保存在堆内存中的对象。

垃圾回收

js垃圾回收是这么定义的:找出那些不再继续使用的变量,然后释放其所占用的内存,垃圾回收器会按照固定的时间间隔周期性地执行这一操作。

js使用垃圾回收机制来自动管理内存:

  • 优势:可以大幅简化程序的内存管理代码,降低程序员的负担,减少因长时间运转而带来的内存泄漏问题。
  • 劣势:自动执行意味着程序员无法掌控内存。js没有暴露任何关于内存的api。我们无法强迫其进行垃圾回收,更无法干预内存管理。

js中的垃圾回收策略:

引用计数

引用计数:跟踪记录每个值被引用的次数,如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放。
原理:每次引用加1,被释放时减1,当这个值的引用次数变成0时,就可将其内存空间回收。如下图所示:

图示

标记清除

标记清除是指当变量进入环境时,这个变量标记为“进入环境”;而当变量离开环境时,则标记为“离开环境”,最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。
这里的环境指的时执行环境,它定义了变量或函数有权访问的其他数据,决定了它们的各自行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

图示

V8内存管理机制

限制内存的原因

  • v8最初为浏览器而设计,不太可能遇到大内存的使用场景
  • 防止因为垃圾回收导致的线程暂停的时间过长

V8的回收策略

  • v8采用了一种分代回收的策略,将内存分为两个生代:新生代和老生代。
  • v8分别对新生代和老生代使用不同的垃圾回收算法来提升垃圾回收的效率。

新生代回收机制

新生代回收算法

如上图所示,新生代内存被一分为二,只有一个处于使用中(From),另一个处于闲置状态(To)。在我们分配内存空间时,先在From空间中进行分配,在垃圾回收运行时,会检查From空间中的对象,当obj2需要被回收时将它留在From空间,obj1移动到To空间,然后进行反转,将From空间和To空间互换,进行垃圾回收时会将To空间中的内存进行释放。

新生代对象的晋升

  • 在新生代垃圾回收的过程中,当一个对象经过多次复制后依然存活,他将会被认为是生命周期较长的对象,随后会被移动到老生代中,采用新的算法进行管理
  • 在From和To空间进行反转的过程中,如果To空间中的使用量已经超过25%,那么就将From中的对象直接晋升到老生代内存中。

老生代回收机制

标记清除

标记清除是将需要被回收的对象进行标记,在垃圾回收运行时直接释放相应的地址空间。

标记合并

标记合并是将存活的对象移动到一边,将需要被回收的对象移动到另一边,然后对需要被回收的对象区域进行整体的垃圾回收。

Author: 匡凡
Link: https://kuangfan.github.io/2020/01/07/js/javascript%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
  • 支付宝