js 堆栈内存、新生代和老生代、垃圾回收详聊
要想了解JS内存管理就必须明白存这些js数据的内存又分为:
栈内存和堆内存
一、 栈|堆内存(Stack|Heap)
- 栈(Stack)内存
原始值:Number、String、Boolean、Null、Undefined、Symbol和BigInt
栈内存主要存储原始值数据类型 - 堆(Heap)内存
引用值:Object( Object、Array和Function等)
堆内存主要存储引用值数据类型
案例1
let n = 1;
let b = true;
let s = "string";
let pObj = {name:'dupha',age:18,address:[{"code": "11","name": "北京市","children": [{"code": "1101","name": "市辖区"}]}]};
let pArr = [1,2,3,4,5];
案例2
//声明 + 第一次赋值
let person= {name:'dupha',age:18,address:[{"code": "11","name": "北京市","children": [{"code": "1101","name": "市辖区"}]}]};
//第二次赋值
person = {name:'soeng',age:20,address:[{"code": "11","name": "北京市","children": [{"code": "1101","name": "市辖区"}]}]};
下方算法以谷歌chrome浏览器的V8引擎来讲解
二、垃圾回收常用方法
- 引用计数法reference counting
跟踪对象被引用的次数,如果引用次数是 0,那么该对象不再用到,GC时会将其对象清除
如图:
引用计数存在问题:循环引用的话,对象内存就无法释放
- 标记清除法
从根节点开始,对其进行深度优先遍历,能够达到的标记活跃对象,不能达到的对象标记死对象。
可以看出,碎片化,后续又多了标记整理法(顾名思义就是标记后,再进行碎片化压缩(整理),提高内存的利用率)
类似于硬盘碎片整理
三、新生代和老生代
- 新生代和老生代内存分配
众所周知,计算机操作系统分为 32位 和 64位
新生代和老生代也会给予操作系统的不同位数来进行内存分配
32位 新生代空间: 32MB (to space 和 from space分别16MB),老生代位700MB
64位 新生代空间: 64MB (to space 和 from space分别32MB),老生代位1400MB
node中,可以修改最大内存node --max-old-space-size=4096
//在查看浏览器内存信息
window.performance
- 新生代算法
scavenger算法:
算法执行周期如下:
第一步:js变量分配内存先在from space
上,如果from space
满了,执行下一步
第二步:从from space
上遍历出活跃对象,判断对象是否已经经过一次周期,
是:判断to space
是否超过25%,超过将放入老生代,没超过保持不动
否:将其移至to space
第三步:清空from-Space
第四步:调换to space
和from space
空间
一直循环执行该周期
- 老生代算法
标记-清除-压缩(Mark-Sweep-Compaction)算法:
- 标记阶段(Mark Phase)
- 起始点:从一组根对象(如全局对象)开始。
- 递归访问:递归地访问所有从根对象可达的对象,并将它们标记为“活动”或“可达”。
- 避免重复访问:使用某种数据结构(如位图或颜色标记)来跟踪已访问过的对象,避免重复访问。
- 清除阶段(Sweep Phase)
- 遍历堆内存:在标记阶段完成后,遍历整个老生代的堆内存。
- 释放未标记对象:释放所有在标记阶段中未被标记为可达的对象所占用的内存。
- 压缩阶段(Compaction Phase)
- 移动对象:将存活的对象(即标记阶段中标记为可达的对象)移动到堆内存的一端,使它们紧密排列。
- 更新引用:由于对象在内存中移动了位置,因此需要更新所有指向这些对象的引用,以确保它们指向新的位置。
- 减少碎片:通过压缩操作,可以消除由清除操作产生的内存碎片,使堆内存更加连续和可用。
- 其他(有趣可自行查阅)
V8在扫描和标记上,采用过 广度扫描、全停顿标记、增量标记、三色标记等
清理回收上,采用并行回收,懒性清理等
四、内存泄露有哪些?
- 隐式全局变量
function dupha(){
data = "假设一个很大的Array or Object";
}
通过直接调用(window / globalThis),用完不清除会占用资源。
- 闭包
闭包:函数嵌套函数
在闭包中,如果里面引用外部函数的变量,它被持续保留在内存中,其变量也无法被垃圾收集器回收。
const dupha = function () {
let BigObject = {text:"这是一个很大的OBJ"};
return function (y) {
console.log(y + JSON.stringify(BigObject));
};
};
let f = dupha();
f(1)
注意: 如果闭包方法用完后,最后将其清空(如:f = null,去除闭包方法引用,下一次垃圾收集将被回收)。
- 用完不移除定时器\事件监听
创建了一个定时器,如果没有明确地移除它,有可能引起:内存泄漏,无意义的执行或者报错。
function getInfoByKey(id){
return new Promise((resolve, reject) => {
// 模拟接口异步请求数据
setTimeout(() => {
resolve(`通过Key:${id}来调用返回一个很大的JSON对象`)
}, 1000)
})
}
var getData = getInfoByKey;
var id = 0;
setInterval(async function (){
const data = await getData(id++);
console.log(data);
},5000)
为啥上传不了大文件
文件过大,超过规定内存或者其他原因,采用文件切片
文件属性 | 描述 |
---|---|
File.name | 文件名称 |
File.size | 文件大小(如: 2MB 2*1024*1024 ) |
File.type | 文件类型 |
File.lastModified | 最后修改时间 |
把大文件按照每个2MB/1MB大小进行切割成小文件上传到服务器上。 |
如有不理解或者不准确的欢迎评论/私聊~