[javascript核心-08] V8 内存管理机制及性能优化

这篇具有很好参考价值的文章主要介绍了[javascript核心-08] V8 内存管理机制及性能优化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

V8 内存管理

V8 本身也是程序,它本身也会申请内存,它申请的内存称为常驻内存,而它又将内存分为堆和栈

栈内存

栈内存介绍

  1. 栈用于存放JS 中的基本类型和引用类型指针
  2. 栈空间是连续的,增加删除只需要移动指针,操作速度很快
  3. 栈空间是有限的,若超出栈空间内存,会抛出栈空间溢出错误
  4. 栈是在执行函数时创建的,函数执行完毕后,栈销毁

栈的内存回收机制

  1. 栈中压入一个全局执行上下文,长驻栈底,不会销毁
  2. 每个函数执行时都会创建一个执行上下文(存储函数变量、文法环境、词法环境的对象)
  3. 函数执行结束,立马销毁
  4. 当函数中存在闭包的情况下,函数的执行上下文会被销毁。函数内部被外部引用的变量会被放入堆内存中保存。

堆内存

内存不连续,需要大的内存空间,使用堆

在 32 位系统下是1.4G,64 位下分配 2G

堆内存分类

新生代

新生代内存new space会保存一些生命周期较短的对象,但新创建的对象无法确定其生命周期,因此会先放入新生代内存中。不过它只有 32M 大小

老生代

新生代内存old space会保存一些生命周期较长的对象。如果判断长短?两个周期的垃圾回收之后,如果数据会在新生代内存中,则将其放入老生代内存。

比较大的对象直接放入老生代。

代码空间(code space)

运行时代码空间,存放 JIT已编译代码,是唯一拥有执行权限的内存

大对象空间(large object space)

专门存储大对象,新生代存放不下,单独分配内存存储。GC 不会对其进行垃圾回收

Map Space

存放对象的Map 信息,即隐藏类,它是为了提升对象属性的访问速度的。V8 会为每一个对象创建一个隐藏类,记录对象的属性布局,包括所有属性和偏移量。

垃圾回收机制

何为垃圾?程序执行结束后不再需要的数据。更准确一点,从根节点GCRoot访问不到的就是内存垃圾。

新生代垃圾回收

新生代中有From SpaceTo Space两块区域,新对象会放入From Space中,如果内存满了就开始垃圾回收。

From SpaceTo Space双端队列的数据结构。

执行过程
  1. 从根节点出发,广度优先遍历所有能到达的对象,将存活的对象(可以访问到的对象)按顺序复制到To Space内存中
  2. 遍历完成后,清空To Space内存
  3. From SpaceTo Space角色互换
晋升机制
  1. 经过一次GC 还存活
  2. 达到空间限制

[javascript核心-08] V8 内存管理机制及性能优化,JavaScript核心,JavaScript,javascript,性能优化,V8,前端,内存管理

优点:空间连续,不会造成内存碎片;高效

缺陷:浪费空间

老生代垃圾回收

浏览器不会同时采用标记清除和标记整理两种方式,在多次标记清除之后通过标记整理去处理内存碎片的问题。

标记清除

标记:从根节点出发,深度优先遍历,能够达到的表示活对象,标记为黑色,不能达到的对象表示死对象,标记为白色。

清除:清除死亡对象,释放内存

优点:不需要进行元素移动;不需要空闲区域,节省内存

缺陷:一次标记清楚后,内存空间不连续,会出现许多内存碎片

标记整理

从根节点出发,深度优先遍历,能够达到的表示活对象,标记为黑色,不能达到的对象表示死对象,标记为白色。这个过程中如果发现内存不连续,则进行元素移动,放到前一个元素后面,保证内存的连续。最后将后面的全部清除。

优点:能够解决标记清除带来的内存碎片问题,特点是一边标记一边进行内存整理。

缺陷:需要移动元素

垃圾回收的性能回收

垃圾回收机制属于宏任务,需要 JS 主线程调用。如果执行时间过长,会造成卡顿,阻塞 JS 的执行。

为了不阻塞 JS执行,需要进行优化。优化的思想可以是将大任务拆成任务,分步执行,类似 React的fiber;也可以将一些任务放入后台,由其他线程执行,不占用主线程

并行执行

开启辅助线程来执行新生代的垃圾回收工作,并行执行回收也是会让 JS 进入全停顿的状态,主线程不能进行任何操作,只能辅助线程完成。但由于新生代比较小,因此不会造成长时间的卡顿。

增量标记

老生代内存空间又大,存储的数据又多,因此不适合并行执行的优化,仍然会占用很长的时间。因此采用分批标记,不一次性完成,在 JS 空闲的时候执行。将标记工作分为多个阶段,每个阶段都只标记一部分对象,和主线程穿插进行。

但是这样就会有一个中间状态出现了,因为除了黑色表示活对象,白色表示死对象以外,还需要有一个种来表示还未被标记的状态。

为了支持增量标记,V8 需要支持垃圾回收的暂停和恢复,因此采用黑白灰三色标记。

  1. 黑色表示该节点可被 GC 根节点到达,且其全部子节点已经被标记完毕。

  2. 灰色表示该节点可被 GC 根节点到达,但子节点还没有被标记处理,也表示目前正在处理该节点

  3. 白色表示该节点还未被被 GC 到达,如果 GC 结束还没有处理,就表明无法到达,没有被引用,就会被回收。

惰性清理

增量标记完成后,如果内存足够则不清理,等 JS 执行完毕再清理。

并发回收

主线程执行过程中,辅助线程在后台完成垃圾回收工作。标记操作由辅助线程完成,清理操作由主线程和辅助线程配合完成。

并发和并行:并发是上下文快速切换;并行是同一时刻多个进程同时进行。

内存泄漏

会产生内部泄漏的情况:

隐式全局变量

全局变量不会被垃圾回收,依次要将不再使用的全局变量及时手动设置为null

function fn(){
    a = 10
}

上面的代码中,a就是全局变量永不回收

剥离的 DOM

在界面中移除 DOM 元素时,还应该相应的移除该其他的引用

<div id='container'>
    <div id='box'></div>
</div>

<script>
    let container = document.getElementById('container')
    let box = document.getElementById('box')
    document.body.removeChild(container)
</script>

上面的代码中,DOM 中已经移除了container节点,但是它还被变量container引用,因此不会被垃圾回收。

定时器

若不手动清除定时器,则其到时执行后也不会被垃圾回收,应该用对应的函数清除定时器

function fn(){
    let i = setInterval(()=>{

    },1000)
    clearInterval(i)
}
fn()

事件监听

如果监听函数中使用到了外部对象数据,则需要进行手动清除事件监听,不然不会被垃圾回收

function fn(){
    let data = [1,2,3]
    class App {
        handle = ()=>{
            console.log(data)
        }
        mount(){
            document.addEventListener('click', this.handle)
        }
        unmount(){
            document.removeEventListener('click', this.handle)
        }
    }
    let app = new App()
    return app
}
let app = fn()
app.mount()
app.unmount()

Map/Set

不主动清除不会被主动回收

let arr = [1,2,3]
let set = new Set()
set.add(arr)
arr = null

上面的代码中,即使arr手动指向了null,但是数据仍然不会被垃圾回收,因为只是清除了arr对数据对象的引用,而set还引用了该数据,因此不会被垃圾回收,必须再手动将set设置为null,才能释放arr。但是set中其他数据的引用可能是必须保留的,因此这种情况下最好的方式是使用WeakSet,以及WeakMap

let arr = [1,2,3]
let set = new WeakSet()
set.add(arr)
arr = null

上面的代码arr指向的数组就会被垃圾回收,因为WeakSet的引用是弱引用,不会阻止垃圾回收

console

浏览器会保存我们输出对象的信息数据引用,因此未清理的console也会造成内存泄漏

排查内存泄漏

如何知道是哪里的代码造成了内存泄漏呢?

录制⏺监控

  1. 刷新页面加载
  2. 监控 JS 堆、文档、节点、监听器、GPU
  3. 手动进行 GC,触发垃圾回收

内存占用

查看内存占用及性能分析:Performance

<body>
    <script>
        let arrData = new Array(200000).fill(100)
        console.log(arr)
    </script>
</body>

[javascript核心-08] V8 内存管理机制及性能优化,JavaScript核心,JavaScript,javascript,性能优化,V8,前端,内存管理

能够看到 JS 堆内存是陡峭上升

快照对比:Memory

<body>
    <button id="btn">增加内存使用</button>
    <script>
        let btn = document.getElementById('btn')
        btn.onclick = fn()
        function Person(name){this.name = name}
        function fn(){
            var arr = []
            for (let index = 0; index < 10000; index++) {
                arr.push(new Person(index))
            }
            console.log(arr)
        }
    </script>
</body>

对于上面的代码,可以在执行前面进行两次快照录制,对比内存占用情况

[javascript核心-08] V8 内存管理机制及性能优化,JavaScript核心,JavaScript,javascript,性能优化,V8,前端,内存管理

可以看到Person对象时增加了 10000 次,然后我们去看这些对象是在哪里产生的

[javascript核心-08] V8 内存管理机制及性能优化,JavaScript核心,JavaScript,javascript,性能优化,V8,前端,内存管理

然后我们就可以找到内存泄漏的问题,进行优化处理

[javascript核心-08] V8 内存管理机制及性能优化,JavaScript核心,JavaScript,javascript,性能优化,V8,前端,内存管理

从垃圾回收角度看性能优化

少用全局变量

  1. 全局执行上下文会一直存在于上下文执行栈中,不会被销毁
  2. 查找链条比较长,比较销毁性能
  3. 可以使用局部缓存替代全局变量

通过原型新增方法

减少函数对象方法的内存占用,让对象实例都共用同一个函数对象。

对象结构保持稳定和一致

  1. V8 会为每个对象增加一个隐藏类,如果对象发生改变(属性改变)就会重置隐藏类,而结构相同的对象会共享隐藏类。因此对象属性尽量保持一致,且尽量不要动态添加和删除属性
  2. 隐藏类描述了对象的结构和属性的偏移地址,可以增加查找属性的时间

结构一致,共享隐藏类

let p1 = {name:'flten',age:18}
let p2 = {name:'wall',age:20}

对象结构不稳定,隐藏类频繁重建

let p = {}
p.name = 'flten'
p.name = 22

函数参数尽量保持稳定

V8 中的内联缓存会监听函数执行,记录中间数据,如果参数结构不同优化会失效。因此尽量不要使用动态参数,让参数结构保持稳定。文章来源地址https://www.toymoban.com/news/detail-601720.html

到了这里,关于[javascript核心-08] V8 内存管理机制及性能优化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis内存优化——内存淘汰及回收机制

    本文是系列文章,为了增强您的阅读体验,已将系列文章目录放入文章末尾。👍👍👍 Redis内存淘汰及回收策略都是Redis 内存优化兜底 的策略,那它们是如何进行 兜底 的呢?先来说明一下什么是内存淘汰和内存回收策略: Redis内存淘汰:当Redis的内存使用 超过配置 的限制时

    2024年02月08日
    浏览(41)
  • linux 性能优化-内存优化

    CPU 管理一样,内存管理也是操作系统最核心的功能之一。内存主要用来存储系统和应 用程序的指令、数据、缓存等。 1.1.1.日常生活常说的内存是什么? 我的笔记本电脑内存就是 8GB 的 这个内存其实是物理内存 物理内存也称为主存,大多数计算机用的主存都是动态随机访问内

    2024年02月04日
    浏览(55)
  • Linux性能优化--性能工具:系统内存

    本章概述了系统级的Linux内存性能工具。本章将讨论这些工具可以测量的内存统计信息,以及如何使用各种工具收集这些统计结果。阅读本章后,你将能够: 理解系统级性能的基本指标,包括内存的使用情况。 明白哪些工具可以检索这些系统级性能指标。 每一种系统级Linu

    2024年02月07日
    浏览(38)
  • Linux性能优化--性能工具:特定进程内存

    本章介绍的工具使你能诊断应用程序与内存子系统之间的交互,该子系统由Linux内核和CPU管理。由于内存子系统的不同层次在性能上有数量级的差异,因此,修复应用程序使其有效地使用内存子系统会对程序性能产生巨大的影响。 阅读本章后,你将能够: 确定一个应用程序使

    2024年02月07日
    浏览(47)
  • JavaScript 性能优化

    优化JavaScript代码的性能是开发过程中的一个关键任务,它可以显著提升网站或应用的用户体验。以下是一些优化技巧,涵盖了减少重绘、减少内存占用和合并网络请求等方面: 1. **减少重绘和重排:**    - **使用 CSS3 动画:** 避免使用 JavaScript 实现简单动画,尽量使用 CSS3

    2024年02月12日
    浏览(39)
  • 【Qt 性能优化】 理解与优化Qt信号槽机制 - 提升应用性能的关键策略

    在这个科技日新月异的时代,软件开发不仅仅是编写代码,更是一种艺术。正如著名计算机科学家 Edsger Dijkstra 所说:“计算机科学并不仅仅关于机器,而是更多地关于人的智慧。” Qt框架,作为一个深受广大开发者喜爱的跨平台应用程序和用户界面开发框架,其核心机制之

    2024年02月20日
    浏览(56)
  • 前端性能优化——内存问题

    过高的内存资源占用会导致 Web 应用变慢,甚至崩溃。可以通过 window.performance.memory 查看浏览器的内存限制等信息。 Web 前端开发中存在许多内存问题,下面是一些常见的内存问题: 内存泄漏:当一个对象不再被使用,但仍然占用着内存空间,就会导致内存泄漏问题。在 Web

    2024年02月16日
    浏览(44)
  • linux性能优化-内存原理

    只有内核才可以直接访问物理内存,Linux内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程通过访问虚拟内存来访问内存。 虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同字长(也就是单个 CPU 指令可以处理数据的最

    2024年02月01日
    浏览(36)
  • 解密JavaScript的异步机制:打破单线程限制,提升性能与用户体验

     🎬 江城开朗的豌豆 :个人主页  🔥 个人专栏  :《 VUE 》 《 javaScript 》  📝  个人网站  :《 江城开朗的豌豆🫛 》  ⛺️ 生活的理想,就是为了理想的生活 !   目录 一、JavaScript的异步编步机制 二、事件循环(Event Loop)和任务队列(Task Queue) 三、宏任务和微任务

    2024年02月08日
    浏览(40)
  • springboot项目外卖管理 day08-缓存优化

    导入maven坐标 在项目的pom.xm1文件中导入spring data redis的maven坐标: 配置yml文件 在项目的application.yml中加入redis相关配置: 设置序列化器,编写配置类 在项目中加入配置类RedisConfig: 也可以用StringRedisTemplate就不用配置类 实现思路 前面我们已经实现了移动端手机验证码登录,随机

    2024年02月12日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包