JavaScript性能优化-内存管理(10)

Part2 · 前端工程化实战

JavaScript性能优化

文章说明:本专栏内容为本人参加【拉钩大前端高新训练营】的学习笔记以及思考总结,学徒之心,仅为分享。如若有误,请在评论区支出,如果您觉得专栏内容还不错,请点赞、关注、评论。共同进步!

上一篇:【快速了解TypeScript语言】

本篇主要内容时JavaScript的性能优化内容,包括:内存管理、GC算法介绍、V8引擎等

一、性能优化介绍

  • 性能优化时不可避免的

  • 哪些内容可以看做是性能优化

    任何一种可以提升程序运行效率,降低程序开销的行为,我们都可以看做是一种优化操作。这就意味着在软件开发的过程中,必然存在着很多值得优化的地方。

  • 无处不在的前端性能优化

    特别是在前端开发过程中,性能优化时无处不在的,例如请求资源时的网络、数据的传输方式,开发过程中所使用的的框架等。

本篇的核心是JavaScript语言的优化,具体来说就是认知内存空间的使用,垃圾回收的方式介绍。从而可以让我们编写出高效的JavaScript代码。

内容概要

  • 内存管理
    • 为什么内存需要管理
    • 内存管理的基本流程
  • 垃圾回收与常见的GC算法
  • V8引擎的垃圾回收
    • V8中的GC算法实现垃圾回收

二、内存管理

Memory Management

1.内存为什么需要管理

随着近些年硬件技术的不断发展,同时高级编程语言中也都自带了GC(Garbage Collection)机制,这样的变化,让我们在不需要注意内存使用的情况下,也能够正常的完成相应的功能开发。

function fn() {
    arrList = [];
    arrList[100000] = 'Leo is a coder'
}
fn()

上述函数体内定义一个数组,数组长度足够大,为了当前函数在调用的时,程序可以向内存申请比较大的内存空间。执行函数过程中,我们使用性能检测工具,我们会发现,内存变化如下,内存持续升高,且并没有回落,这就是内存泄漏。内存泄漏会导致我们的页面处于卡顿状态,因此需要对内存进行人为管理。

在这里插入图片描述

2.内存管理介绍

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

JavaScript中的内存管理

和其他语言相通,JavaScript内存管理的流程也是申请内存空间-使用内存空间-释放内存空间。但是由于ECMAScript中并没有提供操作内存的相关API,所以JavaScript语言不能像C或者C++那样,由开发者主动去调用相应的API来完成内存管理。不过,我们仍然可以通过js脚本去演示当前空间的生命周期是怎样完成的。

// 申请空间
let obj = {}
// 使用空间
obj.name = 'Leo'
// 释放空间
obj = null

3.JavaScript中的垃圾回收

JavaScript中的垃圾

  • JavaScript中的内存管理时自动的
  • 对象不再被引用时是垃圾
  • 对象不能从根上访问到时是垃圾

JavaScript中的可达对象

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

引用说明代码示例:

let obj = {name:'leo'};  // obj引用leo对象,全局可达
let bai = obj;  // bai引用leo内存地址
obj = null;  // obj不再引用,但bai依然在引用

可达说明代码示例:

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)
// {
//     o1: {name: 'obj1', next: {name: 'obj2', prev: [Circular]}},
//     o2: {name: 'obj2', prev: {name: 'obj1', prev: [Circular]}},   
// }

可达对象图示

在这里插入图片描述

如果我们在代码中做一些操作,比如使用delete将obj上的o1的应用以及o2中对obj1的应用删除掉,那么出现下面的情况:

在这里插入图片描述

三、GC算法介绍

1.GC定义与作用

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

2.GC里的垃圾是什么

  • 程序中不再需要使用的对象

    function func() {
        name = 'leo';
        return `${name} is a coder`
    }
    func()
    

    上面例子中,当我们函数调用完成后,name不再被需要,因此它成为了一个垃圾

  • 程序中不能再访问到的对象

    function func() {
        const name = 'leo';
        return `${name} is a coder`
    }
    func()
    

    上面例子中,由于使用了const关键字进行声明变量,因此当函数执行结束后,外界无法再访问到它,它也会成为一个垃圾。

3.GC算法是什么

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

常见的GC算法有以下几种:

  • 引用计数
  • 标记清除
  • 标记整理
  • 分代回收

4.引用计数算法

所谓的引用计数法就是给每个对象一个引用计数器,每当有一个地方引用它时,计数器就会加1;当引用失效时,计数器的值就会减1;任何时刻计数器的值为0的对象就是不可能再被使用的。

这个引用计数法时没有被Java所使用的,但是python有使用到它。而且最原始的引用计数法没有用到GC Roots。

  • 核心思想:设置引用数,判断当前引用数是否为0
  • 引用计数器
  • 引用关系改变时修改引用数字
  • 引用数字为0时立即回收

优点:

  1. 可即时回收垃圾,在该方法中,每个对象始终知道自己是否有被引用,当被引用的数值为0时,对象马上可以把自己当做空闲空间链接到空闲链表;
  2. 最大暂停时间短;
  3. 没有必要沿着指针查找;

缺点:

  1. 计数器的增减处理非常繁重;
  2. 计算器需要占用很多位;
  3. 实现繁琐;
  4. 循环引用无法回收;

5.标记清除算法

该算法分为标记清除两个阶段。标记就是把所有活动对象都做上标记的阶段;清除就是将没有做上标记的对象进行回收的阶段。

  • 核心思想:分标记和清除两个阶段完成
  • 遍历所有对象找标记活动对象
  • 遍历所有对象清除没有标记对象
  • 回收相应的空间

在这里插入图片描述

优点:

  1. 实现简单
  2. 与保守式GC算法兼容(保守式GC在后面介绍)

缺点:

  1. 碎片化:如上图所示,在回收过程中会产生被细化的分块,到后面,即时堆中分块的总大小够用,但是却因为分块太小而不能执行分配
  2. 分配速度:因为分块不是连续的,因此每次分块都要遍历空闲链表,找到足够大的分块,从而造成时间短的浪费
  3. 与写时复制技术不兼容:所谓写时复制就是fork的时候,内存空间只引用而不复制,只有当该进程的数据发生变化时,才会将数据复制到该进程的内存空间。这样,当两个进程中的内存数据相同的时候,就能节约大量的内存空间了。而对于标记-清除算法,它的每个对象都有一个标志位来表示它是否被标记,在每一次运行标记-清除算法的时候,被引用的对象都会进行标记操作,这个仅仅标记位的改变,也会变成对象数据的改变,从而引发写时复制的复制过程,与写时复制的初衷就背道而驰了。

6.标记整理算法

标记-整理算法与标记-清理算法类似,只是后续步骤是让所有存活的对象移动到一端,然后直接清除掉端边界以外的内存。

  • 标记整理可以看作是标记清除的增强
  • 标记阶段的操作和标记清除一致
  • 清除阶段会先执行整理,移动对象位置
    在这里插入图片描述
    在这里插入图片描述

优缺点:该算法可以有效的利用堆,但是整理需要花比较多的时间成本

四、V8引擎

1.认识V8

  • V8引擎是一个JavaScript实现,最初由一些语言方面专家设计,后被谷歌收购,随后谷歌对其进行了开源;
  • V8使用c++开发,在运行JavaScript之前,相比其他的JavaScript的引擎转换成字节码或解释执行,V8将其编译成原生机器码(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如内联缓存(Inline caching)等方法来提高性能
  • 有了这些功能,JavaScript程序在V8引擎下的运行速度媲美二进制程序
  • V8支持众多操作系统,如Windows、Linux、Android等,也支持其他硬件架构,如IA32、X64、ARM等,具有很好的可移植和跨平台特性
  • V8内存设限(64位1.5GB,32位700MB)

2.V8垃圾回收策略

  • 采用分代回收的思想
  • 内存分为新生代、老生代
  • 针对不同对象采用不同算法

V8 使用了分代和大数据的内存分配,在回收内存时使用精简整理的算法标记未引用的对象,然后消除没有标记的对象,最后整理和压缩那些还未保存的对象,即可完成垃圾回收。

在这里插入图片描述

V8中常用的GC算法

  • 分代回收
  • 空间复制
  • 标记清除
  • 标记整理
  • 标记增量

3.V8回收新生代对象

年轻分代中的对象垃圾回收主要通过Scavenge算法进行垃圾回收。在Scavenge的具体实现中,主要采用了Cheney算法:通过复制的方式实现的垃圾回收算法。它将堆内存分为两个 semispace,一个处于使用中(From空间)另一个处于闲置状态(To空间)。当分配对象时,先是在From空间中进行分配。当开始进行垃圾回收时,会检查From空间中的存活对象,这些存活对象将被复制到To空间中,而非存活对象占用的空间将会被释放。完成复制后,From空间和To空间的角色发生对换。在垃圾回收的过程中,就是通过将存活对象在两个 semispace 空间之间进行复制。

年轻分代中的对象有机会晋升为年老分代,条件主要有两个:一个是对象是否经历过Scavenge回收,一个是To空间的内存占用比超过限制。

在这里插入图片描述

V8内存分配

  • V8内存空间一分为二
  • 小空间用于存储新生代对象(32M|16M)(From+To)
  • 新生代值得是存货时间较短的对象

新生代对象回收实现

  • 回收过程曹勇复制算法+标记整理算法
  • 新生代内存分为两个等大小空间
  • 使用空间为From,空闲空间为To
  • 活动对象存储于From空间
  • 标记整理后将活动对象拷贝至To
  • From与To交换空间完成释放
  • 回收细节说明:
    • 拷贝过程中可能出现晋升
    • 晋升就是将新生代对象移动至老生代
    • 一轮GC还存活的新生代需要晋升
    • To的使用率超过25%

4.V8回收老生代对象

  • 老年代对象放在右侧老生代区域
  • 64位操作系统1.4G,32位操作系统700M
  • 老年代对象就是指存活时间较长的对象

对于年老分代中的对象,由于存活对象占较大比重,再采用上面的方式会有两个问题:一个是存活对象较多,复制存活对象的效率将会很低;另一个问题依然是浪费一半空间的问题。为此,V8在年老分代中主要采用了Mark-Sweep(标记清除)标记清除和Mark-Compact(标记整理)相结合的方式进行垃圾回收。

老年代对象回收实现

  • 主要曹勇标记清除、标记整理、增量标记算法
  • 首先使用标记清除完成垃圾空间的回收
  • 采用标记整理进行空间优化
  • 采用增量标记进行效率优化

老生代与新生代回收对象细节对比:

  • 新生代区域垃圾回收使用空间换时间
  • 老生代区域垃圾回收不适合复制算法

增量标记优化垃圾回收

在这里插入图片描述

图示中,程序在标记阶段被暂停运行,等待标记完成自动运行,当遇到大块需要标记的对象时,程序需要暂停很长一段时间,对用户体验很不友好,因此采用增量标记,将一大块分解为多个小块进行标记,减少每次程序暂停的时长,优化用户体验。最后标记完成后统一进行回收。

5.V8垃圾回收总结

  • V8是一款主流的JavaScript引擎
  • V8设置内存上限
  • V8采用基于分代回收思想实现垃圾回收
  • V8内存分为新生代和老生代
  • V8垃圾回收常见的GC算法

今日分享就到了这里,上面很多的概念性问题,要完全的理解并使用这些新的知识,需要很长一段时间。多用、多查、多做!
下面一节我们讲一讲JavaScript的性能优化问题中工具的使用以及代码优化实例

记录:2020/11/11

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

5coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值