首先让我们来问一下GPT:js的栈内存和java的栈内存有什么区别?
JavaScript 和 Java 的栈内存有一些显著的区别,主要体现在语言设计和内存管理的不同上。以下是一些关键点:
栈内存的基本概念 栈内存:用于存储局部变量、函数调用信息等,它的管理方式是先进后出(LIFO)。每当函数被调用时,会在栈上分配空间,函数调用结束后,这些空间会被释放。
JavaScript 中的栈内存
执行环境:JavaScript 是单线程的,运行在浏览器中或 Node.js 环境下。每次函数调用都会创建一个执行上下文,这个上下文会被放入栈中,处理完成后会从栈中移除。
内存管理:JavaScript 的内存管理包括栈和堆。栈用于存储基本数据类型(如 number、string、boolean)和函数调用上下文,而对象、数组等复杂数据类型则存储在堆内存中。
垃圾回收:JavaScript 自动进行垃圾回收,程序员不需要手动管理内存。栈内存中的数据会在函数执行完成后自动回收。
- Java 中的栈内存 ...
这里面的的内存管理部分完全是错误的。先说结论,js的内存以堆为主,栈内存只保留某些类型的变量引用。所有变量的值,都是存储在堆内存中。(V8中部分小整数正负2^53区间内可能采用指针表示法,特殊实现,不予讨论)
我们可以通过几个简单的例子来快速验证一下这个结论。
测试环境:MacOS 14.1 / Node 18.20.3
测试1:最大函数调用栈深度 -- 13960
let i = 0
function main() {
i += 1;
main();
}
try {
main();
} catch (error) {
console.error(error);
console.log("i:", i);
}
// $ node stack-test.js
// RangeError: Maximum call stack size exceeded
// at main (/Users/henry/stack-test.js:3:3)
// at main (/Users/henry/stack-test.js:4:3)
// at main (/Users/henry/stack-test.js:4:3)
// at main (/Users/henry/stack-test.js:4:3)
// at main (/Users/henry/stack-test.js:4:3)
// at main (/Users/henry/stack-test.js:4:3)
// at main (/Users/henry/stack-test.js:4:3)
// at main (/Users/henry/stack-test.js:4:3)
// at main (/Users/henry/stack-test.js:4:3)
// at main (/Users/henry/stack-test.js:4:3)
// i: 13960
测试2:入栈变量最大数目 -- 125635 @ 984KB
let j = 0;
try {
let source = "";
for (j = 0; j < process.argv.at(-1); j++) {
source += `let v_${j} = 0;`; // 0 可以改为任意类型的数据,不影响结果
}
new Function(source)();
} catch (error) {
console.error(error);
console.log("j:", j);
}
// $ time node stack-test.js 125635
// RangeError: Maximum call stack size exceeded
// at eval (eval at <anonymous> (/Users/henry/stack-test.js:8:5), <anonymous>:3:11)
// at Object.<anonymous> (/Users/henry/stack-test.js:8:25)
// at Module._compile (node:internal/modules/cjs/loader:1364:14)
// at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
// at Module.load (node:internal/modules/cjs/loader:1203:32)
// at Module._load (node:internal/modules/cjs/loader:1019:12)
// at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:128:12)
// at node:internal/main/run_main_module:28:49
// j: 125630
// node stack-test.js 125630 0.24s user 0.06s system 100% cpu 0.301 total
// 小于125630可以一直运行下去
// $ time node stack-test.js 125634
// ^C
// node stack-test.js 125629 8.31s user 0.57s system 107% cpu 8.254 total
通过--stack-size=985修改栈内存大小之后,可以得出以下推论:
- 加1KB大小,可以多定义127个变量。
- 1个变量约等于8字节(1024 ÷ 127 ≈ 8)。
- 基础栈内存开销约为3KB(125635 * 8 ÷ 1024 - 984 ≈ 3)。
测试3:闭包捕获导致栈内存逃逸到堆内存
let j = 0;
const limit = +process.argv.at(-1);
try {
do {
let source = "";
let callbackBody = "";
for (j = 0; j < limit; j++) {
source += `let v_${j} = 1;`;
callbackBody += `v_${j};`; // 构建闭包函数,捕获变量
}
new Function(`${source}\n()=>{${callbackBody}}`)();
} while (true);
} catch (error) {
console.error(error);
console.log("j:", j);
}
结语:
如果面试官让我讲一下js的栈内存,我会做如下总结:
- node的默认栈内存大小为948KB
- 单个变量的引用开销约为8字节
- 不管是基础类型还是复杂类型的数据值都是存储在堆内存中,而不是栈内存
- 闭包捕获后的变量不再消耗栈内存
- 理想情况下,函数调用深度不能超过13960层,整个应用内,未被闭包捕获的变量不能超过125630个。
讲人话版本:
栈内存在js开发流程中无需关注。
根本原因分析:
某些技术不过关的搜索引擎在处理"java栈内存"和"javascript栈内存"的时候出现问题。有些复制黏贴怪,直接在自己的博客网站上复制黏贴了搜索出来的结果。谎话说了一千遍,就被人当成了真理。