发布于 2024 年 2 月 26 日,星期一
Node.js中的垃圾回收机制本质上是自动内存管理的一部分,通过V8引擎实现。它通过标记-清除算法识别并回收不再使用的内存,确保内存高效利用。垃圾回收的核心在于识别和释放不再被引用的对象,避免内存泄漏。这一机制在后台自动运行,开发者无需手动干预,但了解其工作原理有助于优化性能和避免潜在问题。
前言
极度投入,深度沉浸,边界清晰
前端小菜鸡一枚,分享的文章纯属个人见解,若有不正确或可待讨论点可随意评论,与各位同学一起学习~欢迎关注
『非同质前端札记』
公众号 ,一起探索学习前端技术......
公众号回复加群
或扫码
, 即可加入前端交流学习群,长期交流学习......
公众号回复加好友
,即可添加为好友
文章分为最后总结和步骤讲解,可自需查看。废话不多数,先上总结。
From space
和 To space
, 例如我们声明一个新对象,这个新对象会被放入 From space
中,当 From space
快满的时候,会遍历所有的对象,将活跃对象从 From space
copy 到 To space
中。在这个过程中,如果一个对象被 copy 了很多次,就会被认为是存活时间较长的对象,将会被放入老生代中。Mark-Sweep (标记清除)算法
,它会从执行栈和全局对象上找所有能访问到的对象,将他们标记为活跃对象,标记完成之后,进入清除阶段,将没有标记的对象清除(这个过程也就是将清除了对象的内存标记为空闲状态),最后,将空闲状态的内存进行释放。内存机制
Chrome V8管理的部分(Javascript使用的部分)
,系统底层管理的部分(C++/C使用的部分)
Chrome V8 的内存管理机制
常驻内存
, 常驻内存
由以下几部分组成:
内存C/C++的部分
Node 中的 js 引擎也是 chrome 的 V8 引擎,所以垃圾回收机制也属于 V8 中的内部垃圾回收机制。
js 中的对象都是保存在堆内存中,在创建进程时,会分配一个默认的堆内存,当对象越来越大时,堆内存会动态的扩大,如果达到最大限制,堆内存就会溢出抛出错误,然后终止 node.js 进程。
V8 的垃圾回收机制根据对象的存活时间采用了不同的算法,内存主要分为新生代和老生代。
新生代(存活时间较短的对象):
老生代(存活时间较长的对象):
老生代的空间就比新生代要大得多了,放的是一些存活时间长的对象,用的是 Mark-Sweep (标记清除)算法。
标记清除的过程:
S: 老生代采取的是标记清除算法,遍历所有对象并标记仍然存活的对象,然后再清除阶段将没有标记的对象进行清除,最后将清除后的空间进行释放。
老生代 | 新生代(默认) | 新生代(最大) | |
---|---|---|---|
64位系统 | 1400MB | 32MB | 64MB |
32位系统 | 700MB | 16MB | 32MB |
注:垃圾回收是影响性能的因素之一,要尽量减少垃圾回收,尤其是全堆垃圾回收
对象存活时间 | 内存空间 | |
---|---|---|
老生代 | 存活时间较长或常驻内存的对象 | –max-old-space-size命令设置老生代内存空间的最大值 |
新生代 | 存活时间较短的对象 | –max-new-space-size命令设置新生代内存空间的大小 |
Q:V8 引擎为什么要将内存分为新老生代呢?
Q: V8 为什么要限制堆内存的大小?
Q: 如何让内存不受限制?
Q:如何查看内存信息?
process.memoryUsage()
方法拿到内存相关信息
process.memoryUsage();
// output:
{
rss: 35454976,
heapTotal: 7127040,
heapUsed: 5287088,
external: 958852,
arrayBuffers: 11314
}
/**
* unit(单位):byte(字节)
rss:常驻内存大小(resident set size),包括代码片段、堆内存、栈等部分。
heapTotal:V8 的堆内存总大小;
heapUsed:占用的堆内存;
external:V8 之外的的内存大小,指的是 C++ 对象占用的内存,比如 Buffer 数据。
arrayBuffers:ArrayBuffer 和 SharedArrayBuffer 相关的内存大小,属于 external 的一部分
*/
Q: 如何测试最大内存限制?
const format = function (bytes) {
return (bytes / 1024 / 1024).toFixed(2) + "MB";
};
const printMemoryUsage = function () {
const memoryUsage = process.memoryUsage();
console.log(
`heapTotal: ${format(memoryUsage.heapTotal)}, heapUsed: ${forma(
memoryUsage.heapUsed
)}`
);
};
const bigArray = [];
setInterval(function () {
bigArray.push(new Array(20 * 1024 * 1024));
printMemoryUsage();
}, 500);
// result:
heapTotal: 164.81 MB, heapUsed: 163.93 MB
heapTotal: 325.83 MB, heapUsed: 323.79 MB
heapTotal: 488.59 MB, heapUsed: 483.84 MB
...
heapTotal: 4036.44 MB, heapUsed: 4003.37 MB
heapTotal: 4196.45 MB, heapUsed: 4163.29 MB
<--- Last few GCs --->
[28033:0x140008000] 17968 ms: Mark-sweep 4003.2 (4036.4) -> 4003.1 (4036.4) MB, 2233.8 / 0.0 ms (average mu = 0.565, current mu = 0.310) allocation failure scavenge might not succeed
[28033:0x140008000] 19815 ms: Mark-sweep 4163.3 (4196.5) -> 4163.1 (4196.5) MB, 1780.3 / 0.0 ms (average mu = 0.413, current mu = 0.036) allocation failure scavenge might not succeed
<--- JS stacktrace --->
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
...
Q: Javascript 的部分是由 ChromeV8 接管的吗?那为什么仍然可以使用大量内存创建缓存呢?
Q: 如何高效使用内存?
var foo = function() {
var bar = function() {
var local = "内部变量";
return function() {
return local;
};
};
var baz = bar();
console.log(baz());
};
// 从上面代码知bar()返回一个匿名函数,一旦 有变量引用它,它的作用域将不会释放,直到没有引用。
// 注:把闭包赋值给一个不可控的对象时,会导致内存泄漏。使用完,将变量赋其他值或置空
Q: 内存泄露?
var arr = [];
exports.hello = function() {
arr.push("hello" + Math.random());
};
//局部变量arr不停增加内存占用,且不会释放,如果必须如此设计,要提供释放接口
var memwatch = require('memwatch');
memwatch.on('leak', function(info) {
console.log('leak:');
console.log(info);
});
memwatch.on('stats', function(stats) {
console.log('stats:') console.log(stats);
});
stats: {
num_full_gc: 4, // 第几次全堆垃圾回收
num_inc_gc: 23, // 第几次增量垃圾回收
heap_compactions: 4, // 第几次对老生代整理
usage_trend: 0, // 使用趋势
estimated_base: 7152944, // 预估基数
current_base: 7152944, // 当前基数
min: 6720776, // 最小
max: 7152944 //最大
}
Q:(question)
R:(result)
A:(attention matters)
D:(detail info)
S:(summary)
『非同质前端札记』
公众号 ,一起探索学习前端技术......加群
或 扫码
, 即可加入前端交流学习群,长期交流学习......加好友
,即可添加为好友