JavaScript 垃圾回收原理与 V8 分代回收

在我们写JavaScript的时候,我们很少手动释放内存。不像 C/C++ 需要使用free(), JS 引擎会帮我们自动回收内存。

但是,自动回收 != 不需要理解内存,很多前端的性能问题,页面卡顿,内存暴涨,其实都和垃圾回收机制有关。

因此理解垃圾回收机制也尤为重要。

什么是垃圾回收?

垃圾回收(Garbage Collection,简称 GC):

自动识别“无法再访问”的对象,并释放其占用的内存。

核心问题只有一个:如何判断一个对象“没用了”?

那么基于这个定义,没有被引用的对象,就是垃圾。比如

let obj = { name: "Tom" } obj = null 

obj = null 后:

  • 原来的对象不再被引用
  • 成为垃圾
  • 等待回收

内存模型基础

那么,JavaScript的内存中主要可以分为两个部分

  • 栈(Stack)—— 存放基本类型和引用地址
    • 基本类型(number、string、boolean、null、undefined、symbol、bigint)
    • 函数的引用地址
  • 堆(Heap)—— 存放对象
    • 对象
    • 数组
    • 函数
    • 闭包
    • DOM 引用

比如:

let obj = { name: "Tom" } 
  • obj 存在栈中,因为这个是引用地址,{ name: "Tom" } 存在堆中

垃圾回收核心原理

那么怎么知道对象是否是垃圾

标记清除算法

js引擎中有标记清楚算法用来判断是否是垃圾,整个过程就是从root出发,看看是否能够被访问到

Root包括:

  • 全局对象(浏览器中的 window,Node 中的 global
  • 当前调用栈中的变量
  • 正在执行函数里的局部变量
  • 闭包中被引用的变量

那么整个算法过程就分为标记阶段与清楚阶段

  1. 标记阶段

假设我们目前有以下这些对象

Root ├── A │ └── B └── C D 
  • A 被 Root 引用
  • B 被 A 引用
  • C 被 Root 引用
  • D 没有人引用

整个过程就是先从root出发,先找到了A与C,为A与C打上了“可达” 标记

继续向下遍历通过A找到了B,给B打上了可达标记,整个过程就是一次深度优先搜索(DFS)或广度优先搜索(BFS)

遍历结束后:

  • A、B、C 被标记为“可达”
  • D 没被访问到
  1. 清除阶段

引擎会扫描整个堆内存。

对于每一个对象:

  • 如果有“标记” → 保留
  • 如果没有标记 → 释放内存

因此D将会被回收。

D(无标记) 

我们知道js是单线程的,那么也就是在垃圾回收的时候,主线程暂停,会执行这个垃圾回收,那么如果频繁进入垃圾回收就会造成页面卡顿。

V8 的分代回收机制

在早期垃圾回收模型中,所有对象都被一视同仁地扫描和回收。但在实际运行中,V8 发现了一个非常重要的现象:

绝大多数对象的生命周期都非常短。

例如:

function render() { let temp = { x: 1, y: 2 } return temp.x } 

在这个函数中,temp 只在函数执行期间存在。函数执行结束后,它就不再被引用,很快就会成为垃圾对象。

但与此同时,也存在一些对象会贯穿整个应用生命周期:

const config = { ... } 

这些对象几乎不会被销毁。

如果所有对象都使用同一种回收策略:

  • 要么频繁扫描整个堆(性能浪费)
  • 要么回收不及时(内存膨胀)

因此,V8 采用了 分代垃圾回收机制(Generational GC)

思想介绍

分代回收机制,将内存分成了两类

  • 新生代(Young Generation)
  • 老生代(Old Generation)

核心思想就是 新创建的对象大概率会很快死亡;存活较久的对象,大概率会继续存活。

新生代

首先,新创建的对象都先放在新生代,这个空间会比较小,大小通常比较小就几MB,并且这个空间回收频率快,回收速度高。

整个新生代又分成两个空间,

  • From Space
  • To Space

具体新生代使用的是 复制算法(Copying Collection),也称为 Scavenge 算法。

算法流程:

From Space: [A][B][C][D] To Space: [空] 

其中:

  • A、C 还被引用
  • B、D 已经不可达

当垃圾回收执行时:

1️⃣ 停止 JS 执行

2️⃣ 从 Root 出发标记存活对象(这里与标记清除有区别,这里是标记复制,但是标记的流程是一样的)

3️⃣ 把“活着的对象”复制到 To Space

变成如下形式

To Space: [A][C] 

4️⃣ 清空整个 From Space

5️⃣ 交换两个空间角色

然后等待下一次执行

老生代

那么什么时候对象会进入老生代呢

进入老生代的条件是,:

  • 在新生代经历多次垃圾回收仍然存活
  • 对象太大

老生代的特点就是

  • 存放生命周期较长的对象
  • 空间更大
  • 回收频率更低
  • 回收成本更高

我们已经知道新生代的算法是复制算法,但是老生代使用的算法是

  • Mark-Sweep(标记清除)
  • Mark-Compact(标记整理)

标记清楚我们已经介绍过了,那么这里我们介绍标记整理

在清除后,整个内存空间会存在很多空隙碎片,那么就需要进行整理:

[A][ ][B][ ][ ][C] |转换成下面的形式 [A][B][C][ ][ ][ ] 

对象会被“挪动”到连续空间。

整个过程

我们可以看下面这个流程。

Root ↓ 新对象 → 新生代(复制算法) ↓(存活多次) 晋升 ↓ 老生代(标记清楚 + 标记整理) 

以上是整个分代回收基本流程,如果有兴趣可以再看看 写屏障等改进方法

内存泄漏是什么

内存泄漏就是该回收的对象一直被引用,导致无法回收。

具体有

1️⃣ 意外的全局变量

functiontest() { a=10// 忘记 var / let } 

2️⃣ 定时器未清除

setInterval(() => {},1000) 

3️⃣ 闭包持有大对象


function bibao() { let data = ... return function() { console.log(data) } } const test = bibao() // 当 test不再被需要,仍然保持着对data引用,导致内存泄漏 

如何减少或避免内存泄漏

为了避免内存问题,我们就需要针对上面可能出现的问题进行针对性解决

比如

  • 少创建全局变量
  • 用完及时清除引用
  • 清除定时器
  • 避免不必要的闭包
  • 避免缓存巨大对象

总结

JavaScript 在 V8 中采用基于可达性的分代垃圾回收机制,通过复制算法优化短生命周期对象,通过标记清除与整理管理长生命周期对象

此外,如果有兴趣可以再了解 写屏障和记忆集解决跨代引用问题。

以上如有错误欢迎评论

Read more

鸿蒙APP开发从入门到精通:鸿蒙电商购物车全栈项目——订单管理、支付管理、AI原生

鸿蒙APP开发从入门到精通:鸿蒙电商购物车全栈项目——订单管理、支付管理、AI原生

《鸿蒙APP开发从入门到精通》第14篇:鸿蒙电商购物车全栈项目——订单管理、支付管理、AI原生 📱💳🤖 内容承接与核心价值 这是《鸿蒙APP开发从入门到精通》的第14篇——订单管理、支付管理、AI原生篇,100%承接第13篇的「用户管理、商品列表、购物车」项目架构,完成鸿蒙电商购物车全栈项目的核心业务功能实现。 学习目标: * 掌握订单管理的设计与实现; * 实现创建订单、查看订单、取消订单; * 理解支付管理的设计与实现; * 实现微信支付、支付宝支付; * 掌握AI原生的设计与实现; * 实现AI搜索、AI推荐、AI客服; * 优化订单管理、支付管理、AI原生的用户体验(响应速度、数据安全、用户反馈)。 学习重点: * 鸿蒙APP订单管理的开发流程; * 订单管理的分类与使用场景; * 支付管理的设计与实现; * AI原生的设计与实现。 一、 订单管理基础 🎯 1.1 订单管理定义 订单管理是指对应用的订单进行管理,主要包括以下方面:

By Ne0inhk

304M参数引爆AIGC效率革命:AMD Nitro-E如何重新定义图像生成范式

304M参数引爆AIGC效率革命:AMD Nitro-E如何重新定义图像生成范式 【免费下载链接】Nitro-E 项目地址: https://ai.gitcode.com/hf_mirrors/amd/Nitro-E 导语 AMD推出仅304M参数的Nitro-E轻量级扩散模型,以1.5天训练周期和39.3样本/秒的吞吐量重新定义行业标准,推动边缘设备实时AI创作普及。 行业现状:轻量化成为AIGC部署关键 2025年全球多模态大模型市场规模预计达156.3亿元,其中图像生成技术贡献超过40%商业价值。当前主流扩散模型普遍面临"三重困境":参数量动辄数十亿导致训练成本高昂、推理速度慢难以满足实时需求、部署门槛高限制边缘应用。根据PPIO最新报告,非推理模型使用量已从3月起持续超过推理模型,反映行业对高效生成技术的迫切需求。 如上图所示,中心发光的网络球体象征AI模型核心,周围多块屏幕展示自然风景(Nitro-E生成的图像示例),地面电路板状线条体现技术架构,直观呈现了高效多模态扩散Transformer的创新设计。这一可视化清晰揭示了模型如何通过令牌压缩技术实现30

By Ne0inhk
Flutter 组件 ansi_styles 的鸿蒙化适配实战 - 驾驭极致终端交互艺术、实现 OpenHarmony 开发链路、日志系统与控制台的工业级色彩分级方案

Flutter 组件 ansi_styles 的鸿蒙化适配实战 - 驾驭极致终端交互艺术、实现 OpenHarmony 开发链路、日志系统与控制台的工业级色彩分级方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 ansi_styles 的鸿蒙化适配实战 - 驾驭极致终端交互艺术、实现 OpenHarmony 开发链路、日志系统与控制台的工业级色彩分级方案 前言 在鸿蒙(OpenHarmony)生态的底座开发、高性能服务端侧逻辑构建、或者是对命令行交互(CLI)有极其严苛要求的自动化工程流水线中。“终端日志的可视化分级与视觉重心引导维度”是衡量整个底层调试链路效能的最终质量门禁。面对包含数万行内核日志、海量网络请求报文、甚至是 0308 批次重型打包过程产生的满屏文字流。如果仅仅依靠终端中苍白的一串 White 和 Black 或者是毫无温标感的 txt 控制台。不仅会导致在定位历史回退(Regression)时让开发工程师如同在字符废墟中盲人摸象。更会因为缺乏大局观的报错优先级呈现。令技术高层在跨终端指挥调度时陷入严重的信息盲区。 我们需要一种“色彩生动、警示分明”的终端资产汇报艺术。 ansi_styles 是一套专注于无缝整合全球公认顶级

By Ne0inhk
通义万相 2.1 × 蓝耘智算:AIGC 界的「黄金搭档」如何重塑创作未来?

通义万相 2.1 × 蓝耘智算:AIGC 界的「黄金搭档」如何重塑创作未来?

我的个人主页我的专栏:人工智能领域、java-数据结构、Javase、C语言,希望能帮助到大家!!!点赞👍收藏❤ 引言 在当今数字化浪潮席卷的时代,AIGC(生成式人工智能)领域正以惊人的速度发展和变革。通义万相 2.1 和蓝耘智算,如同两颗璀璨的新星,它们的携手合作成为了 AIGC界备受瞩目的焦点。这一「黄金搭档」正凭借各自独特的优势,为内容创作带来前所未有的变革,重塑着 AIGC 领域的未来版图。如今,通义万相 2.1 与蓝耘智算的强强联合,正在为这一行业注入新动力,成为 AIGC 界的“黄金搭档”。这次合作不仅将 AI 技术带入更多创意领域,还将加速智能创作工具的普及,带来前所未有的行业变革。 接下来,让我们深入探究它们是如何发挥强大合力,引领创作新时代的。 一、技术联姻:通义万相 2.1

By Ne0inhk