JavaScript性能优化1——内存管理(JS垃圾回收机制引用计数标记清除标记整理)

Posted JIZQAQ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript性能优化1——内存管理(JS垃圾回收机制引用计数标记清除标记整理)相关的知识,希望对你有一定的参考价值。

目录

一、导入

二、内存管理

1.JavaScript中的内存管理

2.JavaScript中的垃圾回收

概念

可达对象

引用样例

可达样例

3.GC算法介绍

GC定义与作用

GC里的垃圾是什么

GC算法是什么

常见GC算法

3.1引用计数

实现原理

引用计数的优点

引用计数的缺点

什么是循环引用的对象

3.2标记清除

实现原理

标记清除优点

标记清除缺点

3.3标记整理

实现原理

标记整理优点

标记整理缺点


一、导入

随着我们javascript代码需要实现的功能越来越复杂,性能优化变得重要了起来。那么哪些内容可以被看做是性能优化呢?本质上来说,任何一种提高运行效率,降低运行开销的行为都可以看做是优化操作。

前端优化无处不在,例如请求资源时候用到的网络、数据的传输方式、开发过程中使用的框架等。本阶段讨论的核心是JavaScript语言本身的优化,也就是从认知内存空间的实用到垃圾回收的方式介绍。从而让我们编写出高效的JavaScript代码。

在这篇文章里,主要讨论内存管理相关的内容。

随着这些年来的硬件不断发展,同时高级编程语言当中都自带GC机制,这些变化都让我们可以在不需要过多注意内存空间的使用下,也能正常完成相应的功能开发。

二、内存管理

  • 内存:由可读写单元组成,表示一篇可操控空间
  • 管理:人为的去操作一片空间的申请、使用和释放
  • 内存管理:开发者主动申请空间、使用空间、释放空间

1.JavaScript中的内存管理

分为下面三个步骤

  • 申请内存空间
  • 使用内存空间
  • 释放内存空间
//DEMO2
// Memory management
// 申请
let obj = {}
// 使用
obj.name = 'lg'
// 释放 js中没有释放api,我们这里就把它设置为null
obj = null

2.JavaScript中的垃圾回收

概念

对我们前端而言JS的内存管理是自动的,每次我们创建一个对象、数组、函数的时候会自动分配一个内存空间。

那么什么是垃圾呢?

  • 对象不再被引用的时候就是垃圾
  • 对象不能从根上访问到的时候也是垃圾。

知道什么是垃圾之后,JS引擎就会出来工作,把它们占据的对象空间进行回收,这就叫做JS的垃圾回收。

下面我们引入一个概念,叫可达对象

可达对象

  • 可以访问到的对象就是可达对象(引用、作用域链)
  • 可达的标准就是从根出发是否能够被找到
  • JavaScript中的根就可以理解为是全局变量对象

接下来我们在代码中看一下JS的引用和可达是什么样的

引用样例

// reference
let obj = {name: 'xm'}// 这里就发生了引用,obj也是可达的,xm空间也是可达的

let ali = obj // xm空间又多了一次引用

obj = null // obj引用xm的这条路断了,但是xm空间还是可达的,因为ali还在引用xm空间

console.log(ali)//打印{ name: 'xm' }

可达样例

先看一下下面这段代码

//DEMO4
//可达对象
//我们定义了一个函数去接收两个变量obj1,obj2,互相指引
function objGroup(obj1,obj2) {
    obj1.next = obj2
    obj2.prev = obj1
    return {
        o1:obj1,
        o2:obj2
    }

}
let obj = objGroup({name:'obj1'},{name:'obj2'})
console.log(obj)

说明:

首先从全局的根出发,我们能找到一个可达的对象obj,它是通过一个函数调用之后,指向了一个内存空间(里面就是o1和o2),又通过相应的属性指向obj1和obj2的空间,这两个空间之间通过next和prev互相指向。

所以在这个例子里面,我们能从根访问到任何一个内存空间。

然后我们删除掉两行代码之后

function objGroup(obj1,obj2) {
    obj1.next = obj2
    //obj2.prev = obj1
    return {
        //o1:obj1,
        o2:obj2
    }

}
let obj = objGroup({name:'obj1'},{name:'obj2'})
console.log(obj)

这之后,我们所有能够找到obj1的线条都被删除了,于是obj1空间就会被认为是垃圾,js引擎就会找到它把它删除。

3.GC算法介绍

GC定义与作用

  • GC就是垃圾回收机制的简写
  • GC可以找到内存中的垃圾、并释放和回收空间。

GC里的垃圾是什么

  • 程序中不再需要使用的对象(下面例子里的name)

  • 程序中不能再访问到的对象(下面例子里的name)

GC算法是什么

  • GC是一种机制,垃圾回收器完成具体的工作
  • 工作的内容就是查找垃圾释放空间、回收空间
  • 算法就是工作时查找和挥手所遵循的规则

常见GC算法

  • 引用计数:通过一个数字判断当前的是不是垃圾
  • 标记清除:进行工作的时候给到活动对象添加一个标记,判断是否垃圾
  • 标记整理:和标记清除类似,回收过程中做的事情不太一样
  • 分代回收:不同生命周期的对象可以采取不同的收集方式,以便提高回收效率

3.1引用计数

实现原理

核心思想:设置应用数,判断当前引用数是否为0,从而判断是否垃圾对象。数字为0,GC开始工作,将其所在的对象空间回收再释放使用。

引用关系发生改变的时候,引用计数器会主动去修改引用数值。引用数字为0的时候立刻回收。

//DEMO4
//GC 引用计数
const user1 = {age:11}
const user2 = {age:22}
const user3 = {age:33}

const nameList = [user1.age,user2.age,user3.age]

function fn() {
    // 当加上const之后,num1作用域变了,一旦fn()结束之后,我们就再也找不到num1了,引用计数为0,gc就会吧num1的内存空间回收
    const num1 = 1 
    num2 = 2
}

fn()

总结:

靠着我们当前对象身上引用计数器的数值是否为0,从而决定它是不是垃圾对象。 

引用计数的优点

  • 发现垃圾时立即回收
  • 最大限度减少程序暂停(当内存快满的时候就立刻去找引用计数为0的删掉)

引用计数的缺点

  • 无法回收循环引用的对象
  • 时间开销大(因为要时刻监控数值的修改)

什么是循环引用的对象

//DEMO5
//对象之间的循环引用
//虽然obj1和obj2全局作用域下找不到了,但是引用还存在的,obj1和obj2互相在他们的作用域内引用
//用引用计数算法无法释放这部分空间。
function fn() {
    const obj1 = {}
    const obj2 = {}

    obj1.name = obj2
    obj2.name = obj1

    return 'hello world'
}

fn()

3.2标记清除

实现原理

这个原理实现要比引用计数算法更加简单,还能解决一些问题。在后续学习的v8当中会被大量用到。

核心思想:将整个垃圾回收操作分成标记和清楚两个阶段完成。第一个阶段会遍历所有对象,找到活动对象标记。第二个阶段仍然遍历所有对象,把那些身上没有标记的对象进行清除。还会把第一个阶段的标记抹掉,便于GC下次正常工作。通过两次遍历行为,把我们当前的垃圾空间进行回收,最终交给相应的空闲列表去维护。

通过下面图片举例说明一下:

在全局的地方,我们可以找到A、B、C三个可达对象,如果发现他们的下边有child,或者child还有child,它会使用递归的方式去寻找可达对象。所以D、E也会被做上可达标记。从global的链条下是找不到a1,b1的,所以GC工作的时候会认为a1、b1是垃圾,把它清除掉,并且把可达标记都清除掉。要注意最终还会把回收的空间放在一个空闲列表上面,方便后面的程序直接申请空间使用。

标记清除优点

可以解决对象循环引用的回收操作

标记清除缺点

如图显示,左右两侧有从根无法被查找的区域,这种情况下在第二轮进行清除操作的时候,就会直接吧两侧对应的空间回收,然后把释放的空间添加到空闲链表之上。紧接着后续的程序进来,在空闲链表上申请空间。

但是我们从图片上看得到,即时左右两边的空间被释放,但是他们这两块空间被中间的内容给分割着,空出来的内存地址不连续,是分散的。假如如图的例子,左侧空出2个空间,右侧空出1个空间,而我们想要申请一个1.5的空间。我们申请左侧呢,就会空余0.5个空间,而右边空间又不足。

这就是标记清除最大的问题,会造成空间碎片化,不能让我们空间最大化使用。

标记清除不会立即回收垃圾对象。

3.3标记整理

实现原理

标记整理可以看作是标记清除的增强操作,他在标记阶段的操作和标记清除一致,但是清楚阶段会先执行整理,移动对象位置。

下面用图片来更好的 解释一下标记整理回收阶段的过程。

回收之前我们内存摆放位置如下,有着很多活动对象、非活动对象和空闲的空间,当他去执行当前标记操作的时候,会把所有的活动对象进行标记,紧接着会去进行一个整理的操作。

整理会把我们的活动对象进行移动,在地址上变成连续的位置,紧接着将当前活动对象右侧的范围进行回收。

回收之后大概长下面的样子,现在回收到的空间基本上都是连续的,后续可以最大化利用内存释放出来的空间。它会配合着标记清除,在我们的V8引擎当中配合实现频繁的GC操作。

标记整理优点

减少碎片化空间

标记整理缺点

不会立即回收垃圾对象

 

 

以上是关于JavaScript性能优化1——内存管理(JS垃圾回收机制引用计数标记清除标记整理)的主要内容,如果未能解决你的问题,请参考以下文章

Android 性能优化之内存优化

JavaScript 性能优化

js性能优化

javascript性能优化

js性能优化

js 原型