JavaScript 中内存泄漏的常见原因

识别并修复常见的 JavaScript 内存泄漏(Node.js 和 Deno.js)

内存泄漏是一种隐性的威胁,逐渐降低性能,导致崩溃,并增加运营成本。与明显的错误不同,内存泄漏通常很微妙,难以发现,直到它们开始引发严重问题。

内存使用增加会推高服务器成本,并对用户体验产生负面影响。了解内存泄漏的发生方式是解决这些问题的第一步。

理解内存泄漏

内存泄漏发生在您的应用程序分配内存后未能在不再需要时释放这些内存。随着时间的推移,这些未释放的内存块会逐渐积累,导致越来越高的内存消耗。

在像 Web 服务器这样的长期运行进程中,这种情况尤其严重,因为泄漏会导致应用程序消耗越来越多的内存,直到最终崩溃或严重变慢。

理解 Node.js(V8)中的内存使用

Node.js(V8)处理几种不同类型的内存。每种类型在应用程序的性能和资源利用方面都起着关键作用。

内存类型 描述
RSS (Resident Set Size) 为 Node.js 进程分配的总内存,包括内存的所有部分:代码、栈和堆。
Heap Total 为 JavaScript 对象分配的内存。这是分配堆的总大小。
Heap Used 实际被 JavaScript 对象使用的内存。这表示当前堆使用了多少内存。
External 由链接到 JavaScript 对象的 C++ 对象使用的内存。这部分内存是在 V8 堆之外管理的。
Array Buffers 为 ArrayBuffer 对象分配的内存,这些对象用于存储原始二进制数据。

RSS (Resident Set Size):为进程分配的总内存

RSS 指的是 Node.js 进程的总内存占用。它包括为进程分配的所有内存,包括堆、栈和代码段。

1
2
3
4
5
6
7
// rss.js
console.log('Initial Memory Usage:', process.memoryUsage());

setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log(`RSS: ${memoryUsage.rss}`);
}, 1000);

这个脚本每秒记录一次RSS内存使用情况。我们可以观察到总内存占用随时间的变化。

1
2
3
4
5
6
7
8
9
10
11
12
➜ node rss.js
Initial Memory Usage: {
rss: 38502400,
heapTotal: 4702208,
heapUsed: 2559000,
external: 1089863,
arrayBuffers: 10515
}
RSS: 41025536
RSS: 41041920
RSS: 41041920
RSS: 41041920

Heap Total:为 JavaScript 对象分配的内存量

Heap Total 表示 V8 引擎(Node.js 使用的 JavaScript 引擎)为JavaScript对象分配的内存总量。

1
2
3
4
5
6
7
8
9
// heap.js
console.log('Initial Memory Usage:', process.memoryUsage());

const largeArray = new Array(1e6).fill('A');

setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log(`Heap Total: ${memoryUsage.heapTotal}`);
}, 1000);

分配一个大的数组会增加堆的总量。记录的堆总量显示了为 JavaScript 对象分配的内存。

1
2
3
4
5
6
7
8
9
10
11
➜ node heap.js
Initial Memory Usage: {
rss: 38535168,
heapTotal: 4702208,
heapUsed: 2559224,
external: 1089863,
arrayBuffers: 10515
}
Heap Total: 12976128
Heap Total: 12976128
Heap Total: 12976128

Heap Used:实际被对象使用的内存量

Heap Used 指的是当前 JavaScript 对象在堆上使用的内存量。

当我们将对象添加到数组中时,堆使用的内存量会增加。

1
2
3
4
5
6
7
8
9
10
11
12
// heap-used.js
console.log('Initial Memory Usage:', process.memoryUsage());

let data = [];
for (let i = 0; i < 1e6; i++) {
data.push({ index: i });
}

setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log(`Heap Used: ${memoryUsage.heapUsed}`);
}, 1000);

随着更多对象的添加,堆使用的值会增加。

1
2
3
4
5
6
7
8
9
10
11
➜ node heap-used.js
Initial Memory Usage: {
rss: 38748160,
heapTotal: 4702208,
heapUsed: 2559424,
external: 1089863,
arrayBuffers: 10515
}
Heap Used: 2833808
Heap Used: 2847776
Heap Used: 2850800

External:由绑定到 JavaScript 的 C++ 对象使用的内存

External 内存指的是由链接到JavaScriptC++ 对象使用的内存。这些对象通过绑定创建,使 JavaScript 能够与本地代码交互,从而在典型的 JavaScript 堆之外分配内存。

这些内存在 JavaScript 中并不直接可见,但它们仍然增加了应用程序的总内存使用量。

Buffer.alloc 方法分配一个 50MB 的缓冲区,这些内存被追踪为外部内存。

1
2
3
4
5
6
7
8
9
// external.js
const buffer = Buffer.alloc(50 * 1024 * 1024); // 分配 50MB 缓冲区

console.log('Initial Memory Usage:', process.memoryUsage());

setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log(`External Memory: ${memoryUsage.external}`);
}, 1000);

这个例子记录了外部内存的使用情况,反映了缓冲区的分配。

1
2
3
4
5
6
7
8
9
10
➜ node external.js
Initial Memory Usage: {
rss: 39223296,
heapTotal: 4702208,
heapUsed: 2560832,
external: 53518663,
arrayBuffers: 52439315
}
External Memory: 53814435
External Memory: 53814435

Array Buffers:为 ArrayBuffer 对象分配的内存

Array Buffers 是用于 ArrayBuffer 对象的内存。这些对象在 JavaScript 中存储固定长度的二进制数据。

ArrayBufferJavaScript 类型数组系统的一部分,允许您直接处理二进制数据。

这些缓冲区的内存与常规 JavaScript 对象分开追踪。它们通常用于处理原始数据,例如文件或网络协议。

以下是分配 50MB ArrayBuffer 并检查 Node.js 进程初始内存使用情况的示例。

1
2
3
4
5
6
7
8
9
// array-buffer.js
const buffer = new ArrayBuffer(50 * 1024 * 1024); // 50MB ArrayBuffer

console.log('Initial Memory Usage:', process.memoryUsage());

setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log(`Array Buffers: ${memoryUsage.arrayBuffers}`);
}, 1000);
1
2
3
4
5
6
7
8
9
10
➜ node array-buffer.js
Initial Memory Usage: {
rss: 39075840,
heapTotal: 4702208,
heapUsed: 2559496,
external: 53518663,
arrayBuffers: 52439315
}
Array Buffers: 52439315
Array Buffers: 52439315

##JavaScript 中内存泄漏的常见原因

JavaScript 中的内存泄漏通常由以下原因引起:

变量管理不当

未正确管理的变量可能导致内存泄漏。

例如,如果您声明了一些应该是临时的变量,但忘记清理它们,它们将继续占用内存。

1
2
3
4
5
6
7
8
9
10
11
let cache = {};

function storeData(key, value) {
cache[key] = value;
}

// 模拟函数多次调用
storeData('item1', new Array(1000000).fill('A'));
storeData('item2', new Array(1000000).fill('B'));

// 内存泄漏:存储在 'cache' 中的数据从未被释放

在上面的例子中,数据被添加到一个名为 cache 的全局对象中。如果在不再需要时未移除这些数据,它们将继续不必要地占用内存。

这种情况在这些变量存储在全局作用域中时尤其严重,使它们在应用程序的整个生命周期中持续存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
let globalUserSessions = {}; // 全局作用域

function addUserSession(sessionId, userData) {
globalUserSessions[sessionId] = userData; // 在全局作用域中存储用户数据
}

function removeUserSession(sessionId) {
delete globalUserSessions[sessionId]; // 手动移除用户会话
}

// 模拟添加用户会话
addUserSession('session1', { name: 'Alice', data: new Array(1000000).fill('A') });
addUserSession('session2', { name: 'Bob', data: new Array(1000000).fill('B') });

globalUserSessions 是一个全局对象,用于存储用户会话数据。由于它位于全局作用域中,因此在应用程序的整个运行期间都会存在。如果未使用 removeUserSession 正确移除会话数据,数据将无限期地留在内存中,导致内存泄漏。

持久的全局对象

全局对象可能比需要的时间更长地占用内存。它们中的数据即使不再需要也可能继续保留在内存中,从而逐渐增加内存使用量。

1
2
3
4
global.config = {
settings: new Array(1000000).fill('Configuration')
};
// 内存泄漏:'config' 是全局对象,且在整个应用程序生命周期内都会占用内存

由于 config 是全局可访问且从未清理的,它使用的内存在应用程序的整个运行期间都会保留。以下是避免内存泄漏的一种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createConfig() {
return {
settings: new Array(1000000).fill('Configuration')
};
}

// 仅在需要时使用 config,并在之后让它被垃圾回收
function processConfig() {
const config = createConfig();
// 使用 config 进行操作
console.log(config.settings[0]);

// config 在不再被引用时将从内存中清除
}

processConfig();

与其将 config 存储在全局对象中,我们将 config 本地存储在函数内。这样可以确保 config 在函数运行后被清除,释放内存供垃圾回收使用。

未移除的事件监听器

在不再需要时,未正确移除事件监听器可能导致内存泄漏。

每个事件监听器都会保留对函数及其使用的任何变量的引用,从而阻止垃圾回收器回收这些内存。

随着时间的推移,如果不断添加监听器而不移除它们,内存使用量将会增加。

以下是一个展示事件监听器未正确移除时如何导致内存泄漏的例子:

1
2
3
4
5
6
7
8
9
10
11
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

function listener() {
console.log('Event triggered!');
}

// 不断添加事件监听器
setInterval(() => {
myEmitter.on('event', listener);
}, 1000);

每秒都会添加一个新的事件监听器。然而,这些监听器从未被移除,导致它们在内存中不断累积。

每个监听器都持有对监听器函数及其相关变量的引用,阻止垃圾回收并随着时间推移导致内存使用量增加。

为了防止这种内存泄漏,应该在不再需要时移除事件监听器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

function listener() {
console.log('Event triggered!');
}

// 添加事件监听器
myEmitter.on('event', listener);

// 触发事件后移除监听器
myEmitter.emit('event');
myEmitter.removeListener('event', listener);

// 或者,您可以使用 `once` 方法添加一个在触发后自动移除的监听器
myEmitter.once('event', listener);

闭包捕获变量

JavaScript 中的闭包可能会无意间比需要的时间更长地保留变量。当闭包捕获一个变量时,它会保持对该内存的引用。

如果闭包被用于一个长期运行的进程,或者没有正确终止,那么捕获的变量将保留在内存中,导致泄漏。

1
2
3
4
5
6
7
8
9
10
function createClosure() {
let capturedVar = new Array(1000000).fill('Data');

return function() {
console.log(capturedVar[0]);
};
}

const closure = createClosure();
// 闭包会保留对 'capturedVar' 的引用,即使它不再被使用

为了避免泄漏,确保闭包不会不必要地捕获大量变量,或者在不再需要时结束它们。

1
2
3
4
5
6
7
8
9
10
11
function createClosure() {
let capturedVar = new Array(1000000).fill('Data');

return function() {
console.log(capturedVar[0]);
capturedVar = null; // 在不再需要时释放内存
};
}

const closure = createClosure();
closure(); // 使用后 'capturedVar' 被释放

未管理的回调

在某些情况下,如果回调持有变量或对象的时间超过必要时长,未管理的回调可能会导致内存问题。

然而,JavaScript 的垃圾回收器通常能够有效地在引用不再需要时清理内存。

1
2
3
4
5
6
7
8
9
10
11
12
13
function fetchData(callback) {
let data = new Array(1000000).fill('Data');

setTimeout(() => {
callback(data);
}, 1000);
}

function handleData(data) {
console.log(data[0]);
}

fetchData(handleData); // 'data' 数组保留在内存中

在上面的例子中:

  • 数据分配fetchData 函数分配了一个包含 100 万个元素的大数组(data)。
  • 回调引用:当 setTimeout 在 1 秒后调用回调函数(handleData)时,它引用了这个大数组。

尽管进行了大规模的分配,JavaScript 的垃圾回收器确保在引用不再需要时释放内存。

除非处理非常复杂的场景,其中引用被意外保留,否则无需手动清除这些引用。
避免不必要的复杂性
在大多数情况下,无需在标准的异步回调中手动清除引用。

过度复杂(不推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function fetchData(callback) {
let data = new Array(1000000).fill('Data');

setTimeout(() => {
callback(data);
data = null; // 释放引用
global.gc(); // 显式触发垃圾回收
}, 1000);
}

function handleData(data) {
console.log(data[0]);
data = null; // 处理后清除引用
}

console.log('初始内存使用情况:', process.memoryUsage());

fetchData(handleData);

setTimeout(() => {
console.log('最终内存使用情况:', process.memoryUsage());
}, 2000); // 给垃圾回收一些时间

虽然这段代码手动清除了引用并显式触发了垃圾回收,但它引入了不必要的复杂性。
JavaScript 的垃圾回收机制通常足以处理内存清理,不需要这些额外的步骤。在大多数情况下,这种手动干预不仅是多余的,还可能使代码更难维护。

bind() 的错误使用

使用 bind() 创建一个 this 关键字绑定到特定值的新函数。如果使用不当,这可能导致内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
function MyClass() {
this.largeData = new Array(1000000).fill('leak');

window.addEventListener('click', this.handleClick.bind(this));
}

MyClass.prototype.handleClick = function() {
console.log('Clicked');
};

// 如果 MyClass 的实例被销毁,但事件监听器未移除,
// 绑定的函数会导致该实例在内存中保持活跃状态。

内存泄漏发生在 bind() 的原因

  1. 引用被保留:使用 bind() 时,新函数会记住原始函数和 this 值。如果在不再需要时不移除函数,它会继续占用内存。
  2. 大对象保持在内存中:绑定的函数可能意外地使大对象保留在内存中,即使你已经不再需要它们。

循环引用

当两个对象互相引用时,就会发生循环引用。这种情况会造成一个循环,可能会让垃圾回收器无法释放内存。

1
2
3
4
5
6
function CircularReference() {
this.reference = this; // 循环引用
}

let obj = new CircularReference();
obj = null; // 将 obj 设为 null 可能无法释放内存。

即使你将 obj 设为 null,由于存在自引用,内存可能仍然无法释放。

如何避免循环引用
打破循环:确保对象在不再需要时不会相互引用,这有助于垃圾回收器清理它们。

1
2
3
4
5
6
7
8
9
function CircularReference() {
this.reference = this;
}

let obj = new CircularReference();

// 打破循环引用
obj.reference = null;
obj = null; // 现在可以释放内存了

通过将 obj.reference 设为 null,我们打破了循环引用。这使得垃圾回收器在 obj 不再需要时可以释放内存。

使用弱引用:使用 WeakMapWeakSetWeakRef 可以在即使有引用的情况下,垃圾回收器也能清理内存,因为它们是弱引用。

1
2
3
4
5
6
7
8
9
10
let weakMap = new WeakMap();

function CircularReference() {
let obj = {};
weakMap.set(obj, "这是一个弱引用");
return obj;
}

let obj = CircularReference();
// 当对象不再需要时可以被垃圾回收

weakMap 持有对 obj 的弱引用。这意味着当 obj 不再被其他地方使用时,尽管 weakMap 仍然引用它,但它仍然可以被垃圾回收。

1
2
3
4
5
6
7
8
9
10
11
12
13
let weakRef;

function createObject() {
let obj = { data: 'important' };
weakRef = new WeakRef(obj);
return obj;
}

let obj = createObject();

console.log(weakRef.deref()); // { data: 'important' }

obj = null; // 现在对象可以被垃圾回收

weakRef 允许你对 obj 保持弱引用。如果将 obj 设为 null 且没有其他地方引用它,那么即使 weakRef 仍然存在,它也可以被垃圾回收。

快速提示

WeakMapWeakSetWeakRef 在防止内存泄漏方面非常有用,但你可能并不总是需要它们。它们更多用于高级用例,比如管理缓存或大数据。

如果你在开发典型的 Web 应用,可能不常见到它们,但了解它们的存在并在需要时使用是有帮助的。

在 Node.js 中分析内存使用情况

为了找到内存泄漏,你需要分析应用程序,以了解内存的使用情况。

以下是一个用于模拟 CPU 密集型任务、I/O 操作,并故意制造内存泄漏以供测试的 Node.js 应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const http = require('http');
const url = require('url');

// 模拟 CPU 密集型任务
const handleCpuIntensiveTask = (req, res) => {
let result = 0;
for (let i = 0; i < 1e7; i++) {
result += i * Math.random();
}
console.log('内存使用情况 (CPU 任务):', process.memoryUsage()); // 记录内存使用情况
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(`CPU 密集型任务的结果: ${result}`);
};

// 创建一个大的内存缓冲区
const largeBuffer = Buffer.alloc(1024 * 1024 * 50, 'a'); // 用 'a' 填充的 50MB 缓冲区

// 模拟 I/O 操作
const handleSimulateIo = (req, res) => {
// 模拟读取缓冲区,仿佛它是一个文件
setTimeout(() => {
console.log('内存使用情况 (模拟 I/O):', process.memoryUsage()); // 记录内存使用情况
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(`模拟 I/O 操作完成,数据长度: ${largeBuffer.length}`);
}, 500); // 模拟 500ms 的 I/O 操作
};

// 模拟内存泄漏(用于测试)
let memoryLeakArray = [];

const causeMemoryLeak = () => {
memoryLeakArray.push(new Array(1000).fill('memory leak'));
console.log('内存泄漏数组长度:', memoryLeakArray.length);
};

const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);

if (parsedUrl.pathname === '/cpu-intensive') {
handleCpuIntensiveTask(req, res);
} else if (parsedUrl.pathname === '/simulate-io') {
handleSimulateIo(req, res);
} else if (parsedUrl.pathname === '/cause-memory-leak') {
causeMemoryLeak();
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('内存泄漏已触发。检查内存使用情况。');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('未找到');
}
});

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`服务器正在端口 ${PORT} 上运行`);
});

接下来,我们需要对服务器进行压力测试。此脚本通过发送 100 个请求分别模拟 CPUI/O 和内存泄漏来对服务器进行压力测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/bin/bash

# 要发送的请求数量
REQUESTS=100

# 端点 URL
CPU_INTENSIVE_URL="http://localhost:3000/cpu-intensive"
SIMULATE_IO_URL="http://localhost:3000/simulate-io"
MEMORY_LEAK_URL="http://localhost:3000/cause-memory-leak"

echo "发送 $REQUESTS 个请求到 $CPU_INTENSIVE_URL$SIMULATE_IO_URL..."

# 循环 CPU 密集型端点
for ((i=1;i<=REQUESTS;i++)); do
curl -s $CPU_INTENSIVE_URL > /dev/null &
done

# 循环模拟 I/O 端点
for ((i=1;i<=REQUESTS;i++)); do
curl -s $SIMULATE_IO_URL > /dev/null &
done

# 循环内存泄漏端点
for ((i=1;i<=REQUESTS;i++)); do
curl -s $MEMORY_LEAK_URL > /dev/null &
done

wait
echo "完成。"

此脚本使用 curl 循环遍历 URL,并在后台运行它们以模拟高负载。

1
2
3
4
5
➜  ./load_test.sh
发送 100 个请求到 http://localhost:3000/cpu-intensive 和 http://localhost:3000/simulate-io 和

http://localhost:3000/cause-memory-leak
完成。

以下是服务器对压力测试的响应。确保在开始测试前服务器已经运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
➜  node --prof server.js
服务器正在端口 3000 上运行
内存使用情况 (模拟 I/O): {
rss: 122863616,
heapTotal: 17547264,
heapUsed: 8668016,
external: 54075004,
arrayBuffers: 52439275
}
内存泄漏数组长度: 25
内存泄漏数组长度: 26
内存泄漏数组长度: 27
内存泄漏数组长度: 28
内存泄漏数组长度: 29
...
内存泄漏数组长度: 56
内存使用情况 (CPU 任务): {
rss: 122716160,
heapTotal: 17547264,
heapUsed: 11393456,
external: 54075004,
arrayBuffers: 52439275
}
内存泄漏数组长度: 173

分析结果

配置文件数据将保存在类似 isolate-0xXXXXXXXXXXXX-v8.log 的文件中。

要处理日志并获取人类可读的摘要,请运行以下命令:

1
➜  node --prof-process isolate-0x140008000-42065-v8.log > processed-profile.txt

这将生成一个名为 processed-profile.txt 的文件,其中包含 CPU 配置文件的数据,详细说明了您的应用程序花费时间的位置及其如何管理内存。

打开 processed-profile.txt 文件,查找占用大量时间或内存的部分。

isolate-0x140008000-42065-v8.log 中统计的配置文件结果(4099 个 ticks,308 个未计入,0 个排除)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
Statistical profiling result from isolate-0x140008000-42065-v8.log, (4099 ticks, 308 unaccounted, 0 excluded).

[Shared libraries]:
ticks total nonlib name

[JavaScript]:
ticks total nonlib name
1007 24.6% 24.6% JS: *handleCpuIntensiveTask /Users/trevorindreklasn/Projects/labs/node-memory/server.js:5:32
5 0.1% 0.1% JS: +handleCpuIntensiveTask /Users/trevorindreklasn/Projects/labs/node-memory/server.js:5:32
1 0.0% 0.0% JS: ^onParserExecute node:_http_server:839:25
1 0.0% 0.0% JS: ^getKeys node:internal/util/inspect:709:17
1 0.0% 0.0% JS: ^clearBuffer node:internal/streams/writable:742:21
1 0.0% 0.0% JS: ^checkListener node:events:276:23
1 0.0% 0.0% JS: ^Socket node:net:353:16
1 0.0% 0.0% JS: +pushAsyncContext node:internal/async_hooks:539:26
1 0.0% 0.0% JS: +processTicksAndRejections node:internal/process/task_queues:67:35

[C++]:
ticks total nonlib name
2772 67.6% 67.6% t std::__1::__hash_table<std::__1::__hash_value_type<int, std::__1::unique_ptr<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>, std::__1::default_delete<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>>>>, std::__1::__unordered_map_hasher<int, std::__1::__hash_value_type<int, std::__1::unique_ptr<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>, std::__1::default_delete<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>>>>, std::__1::hash<int>, std::__1::equal_to<int>, true>, std::__1::__unordered_map_equal<int, std::__1::__hash_value_type<int, std::__1::unique_ptr<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>, std::__1::default_delete<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>>>>, std::__1::equal_to<int>, std::__1::hash<int>, true>, std::__1::allocator<std::__1::__hash_value_type<int, std::__1::unique_ptr<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>, std::__1::default_delete<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>>>>>>::rehash(unsigned long)

[Summary]:
ticks total nonlib name
1019 24.9% 24.9% JavaScript
2772 67.6% 67.6% C++
358 8.7% 8.7% GC
0 0.0% Shared libraries
308 7.5% Unaccounted

[C++ entry points]:
ticks cpp total name
2636 100.0% 64.3% TOTAL

[Bottom up (heavy) profile]:
Note: percentage shows a share of a particular caller in the total
amount of its parent calls.
Callers occupying less than 1.0% are not shown.

ticks parent name
2772 67.6% t std::__1::__hash_table<std::__1::__hash_value_type<int, std::__1::unique_ptr<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>, std::__1::default_delete<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>>>>, std::__1::__unordered_map_hasher<int, std::__1::__hash_value_type<int, std::__1::unique_ptr<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>, std::__1::default_delete<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>>>>, std::__1::hash<int>, std::__1::equal_to<int>, true>, std::__1::__unordered_map_equal<int, std::__1::__hash_value_type<int, std::__1::unique_ptr<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>, std::__1::default_delete<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>>>>, std::__1::equal_to<int>, std::__1::hash<int>, true>, std::__1::allocator<std::__1::__hash_value_type<int, std::__1::unique_ptr<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>, std::__1::default_delete<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>>>>>>::rehash(unsigned long)
1880 67.8% JS: *handleCpuIntensiveTask /Users/trevorindreklasn/Projects/labs/node-memory/server.js:5:32
1727 91.9% JS: ^<anonymous> /Users/trevorindreklasn/Projects/labs/node-memory/server.js:36:34
1129 65.4% JS: +emit node:events:467:44
1129 100.0% JS: ^parserOnIncoming node:_http_server:1033:26
1129 100.0% JS: ^parserOnHeadersComplete node:_http_common:71:33
598 34.6% JS: ^emit node:events:467:44
598 100.0% JS: ^parserOnIncoming node:_http_server:1033:26
598 100.0% JS: ^parserOnHeadersComplete node:_http_common:71:33
153 8.1% JS: ~<anonymous> /Users/trevorindreklasn/Projects/labs/node-memory/server.js:36:34
140 91.5% JS: ^emit node:events:467:44
140 100.0% JS: ~parserOnIncoming node:_http_server:1033:26
140 100.0% JS: ~parserOnHeadersComplete node:_http_common:71:33
13 8.5% JS: ~parserOnIncoming node:_http_server:1033:26
13 100.0% JS: ~parserOnHeadersComplete node:_http_common:71:33
655 23.6% t std::__1::__hash_table<std::__1::__hash_value_type<int, std::__1::unique_ptr<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>, std::__1::default_delete<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>>>>, std::__1::__unordered_map_hasher<int, std::__1::__hash_value_type<int, std::__1::unique_ptr<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>, std::__1::default_delete<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>>>>, std::__1::hash<int>, std::__1::equal_to<int>, true>, std::__1::__unordered_map_equal<int, std::__1::__hash_value_type<int, std::__1::unique_ptr<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>, std::__1::default_delete<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>>>>, std::__1::equal_to<int>, std::__1::hash<int>, true>, std::__1::allocator<std::__1::__hash_value_type<int, std::__1::unique_ptr<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>, std::__1::default_delete<std::__1::unordered_map<int, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>, std::__1::hash<int>, std::__1::equal_to<int>, std::__1::allocator<std::__1::pair<int const, std::__1::unique_ptr<v8_inspector::InspectedContext, std::__1::default_delete<v8_inspector::InspectedContext>>>>>>>>>>::rehash(unsigned long)
654 99.8% JS: *handleCpuIntensiveTask /Users/trevorindreklasn/Projects/labs/node-memory/server.js:5:32
612 93.6% JS: ^<anonymous> /Users/trevorindreklasn/Projects/labs/node-memory/server.js:36:34
410 67.0% JS: +emit node:events:467:44
410 100.0% JS: ^parserOnIncoming node:_http_server:1033:26
202 33.0% JS: ^emit node:events:467:44
202 100.0% JS: ^parserOnIncoming node:_http_server:1033:26
42 6.4% JS: ~<anonymous> /Users/trevorindreklasn/Projects/labs/node-memory/server.js:36:34
40 95.2% JS: ^emit node:events:467:44
40 100.0% JS: ~parserOnIncoming node:_http_server:1033:26
2 4.8% JS: ~parserOnIncoming node:_http_server:1033:26
2 100.0% JS: ~parserOnHeadersComplete node:_http_common:71:33
49 1.8% JS: ^<anonymous> /Users/trevorindreklasn/Projects/labs/node-memory/server.js:36:34
38 77.6% JS: +emit node:events:467:44
38 100.0% JS: ^parserOnIncoming node:_http_server:1033:26
38 100.0% JS: ^parserOnHeadersComplete node:_http_common:71:33
11 22.4% JS: ^emit node:events:467:44
11 100.0% JS: ^parserOnIncoming node:_http_server:1033:26
11 100.0% JS: ^parserOnHeadersComplete node:_http_common:71:33

1007 24.6% JS: *handleCpuIntensiveTask /Users/trevorindreklasn/Projects/labs/node-memory/server.js:5:32
940 93.3% JS: ^<anonymous> /Users/trevorindreklasn/Projects/labs/node-memory/server.js:36:34
663 70.5% JS: +emit node:events:467:44
663 100.0% JS: ^parserOnIncoming node:_http_server:1033:26
663 100.0% JS: ^parserOnHeadersComplete node:_http_common:71:33
277 29.5% JS: ^emit node:events:467:44
277 100.0% JS: ^parserOnIncoming node:_http_server:1033:26
277 100.0% JS: ^parserOnHeadersComplete node:_http_common:71:33
67 6.7% JS: ~<anonymous> /Users/trevorindreklasn/Projects/labs/node-memory/server.js:36:34
61 91.0% JS: ^emit node:events:467:44
61 100.0% JS: ~parserOnIncoming node:_http_server:1033:26
61 100.0% JS: ~parserOnHeadersComplete node:_http_common:71:33
6 9.0% JS: ~parserOnIncoming node:_http_server:1033:26
6 100.0% JS: ~parserOnHeadersComplete node:_http_common:71:33

308 7.5% UNKNOWN
11 3.6% JS: ^compileForInternalLoader node:internal/bootstrap/realm:384:27
11 100.0% JS: ^requireBuiltin node:internal/bootstrap/realm:421:24
2 18.2% JS: ~<anonymous> node:internal/streams/duplex:1:1
2 100.0% JS: ^compileForInternalLoader node:internal/bootstrap/realm:384:27
2 100.0% JS: ^requireBuiltin node:internal/bootstrap/realm:421:24
2 18.2% JS: ~<anonymous> node:http:1:1
2 100.0% JS: ^compileForInternalLoader node:internal/bootstrap/realm:384:27
2 100.0% JS: ~compileForPublicLoader node:internal/bootstrap/realm:332:25
1 9.1% JS: ~<anonymous> node:net:1:1
1 100.0% JS: ^compileForInternalLoader node:internal/bootstrap/realm:384:27
1 100.0% JS: ^requireBuiltin node:internal/bootstrap/realm:421:24
1 9.1% JS: ~<anonymous> node:internal/streams/readable:1:1
1 100.0% JS: ^compileForInternalLoader node:internal/bootstrap/realm:384:27
1 100.0% JS: ^requireBuiltin node:internal/bootstrap/realm:421:24
1 9.1% JS: ~<anonymous> node:internal/streams/operators:1:1
1 100.0% JS: ^compileForInternalLoader node:internal/bootstrap/realm:384:27
1 100.0% JS: ^requireBuiltin node:internal/bootstrap/realm:421:24
1 9.1% JS: ~<anonymous> node:internal/perf/observe:1:1
1 100.0% JS: ^compileForInternalLoader node:internal/bootstrap/realm:384:27
1 100.0% JS: ^requireBuiltin node:internal/bootstrap/realm:421:24
1 9.1% JS: ~<anonymous> node:internal/child_process:1:1
1 100.0% JS: ^compileForInternalLoader node:internal/bootstrap/realm:384:27
1 100.0% JS: ^requireBuiltin node:internal/bootstrap/realm:421:24
1 9.1% JS: ~<anonymous> node:child_process:1:1
1 100.0% JS: ^compileForInternalLoader node:internal/bootstrap/realm:384:27
1 100.0% JS: ^requireBuiltin node:internal/bootstrap/realm:421:24
1 9.1% JS: ~<anonymous> node:_http_agent:1:1
1 100.0% JS: ^compileForInternalLoader node:internal/bootstrap/realm:384:27
1 100.0% JS: ^requireBuiltin node:internal/bootstrap/realm:421:24

注意以下几点:

  • 高CPU使用率的函数:这些是代码中的瓶颈。
  • 内存密集型函数:消耗大量内存的函数可能暗示潜在的内存泄漏,尤其是那些应该释放内存但没有释放的代码部分。
  • 事件循环和垃圾回收 (GC):如果GC占用了较高比例的时间,这可能表明应用程序在内存管理方面存在问题。

内存泄漏可能很隐蔽,但解决它们对于保持JavaScript应用程序的高效和可靠至关重要。