识别并修复常见的 JavaScript 内存泄漏(Node.js 和 Deno.js)
内存泄漏是一种悄无声息的威胁,它逐渐降低性能,导致崩溃,并增加运营成本。与明显的错误不同,内存泄漏通常很微妙,很难发现,直到它们开始引起严重问题。
增加的内存使用推动了服务器成本的上升,并负面影响用户体验。理解内存泄漏是如何发生的是解决它们的第一步。
理解内存泄漏
当应用程序分配内存,然后在不再需要时未能释放它时,就会发生内存泄漏。随着时间的推移,这些未释放的内存块累积起来,导致内存消耗逐渐增加。
这在像 web 服务器这样的长时间运行的进程中尤为成问题,其中泄漏可能导致应用程序消耗越来越多的内存,直到最终崩溃或慢到几乎无法使用。
理解 Node.js(V8)中的内存使用
Node.js(V8)处理几种不同类型的内存。每种都在应用程序的性能和资源利用中起着关键作用。
内存类型 | 描述 |
---|---|
RSS(常驻集大小) | Node.js 进程分配的总内存,包括内存的所有部分:代码、栈和堆。 |
堆总量 | 为 JavaScript 对象分配的内存。这是分配堆的总大小。 |
堆已使用 | 由 JavaScript 对象实际使用的内存。这显示了当前堆的使用量。 |
外部 | 由链接到 JavaScript 对象的 C++ 对象使用的内存。此内存在 V8 堆外部管理。 |
数组缓冲区 | 为 ArrayBuffer 对象分配的内存,用于保存原始二进制数据。 |
- RSS(常驻集大小): 为进程分配的总内存。
RSS 指的是 Node.js 进程的总内存占用。它包括为进程分配的所有内存,包括堆、栈和代码段。
// rss.js
console.log('初始内存使用情况:', process.memoryUsage());
setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log(`RSS: ${memoryUsage.rss}`);
}, 1000);
这个脚本每秒记录一次 RSS 内存使用情况。我们可以观察到总内存占用随时间的变化。
➜ node rss.js
初始内存使用情况: {
rss: 38502400,
heapTotal: 4702208,
heapUsed: 2559000,
external: 1089863,
arrayBuffers: 10515
}
RSS: 41025536
RSS: 41041920
RSS: 41041920
RSS: 41041920
- 堆总量: 为 JavaScript 对象分配的内存量。
堆总量表示 V8 引擎(Node.js 使用的 JavaScript 引擎)为 JavaScript 对象分配的内存总量。
// heap.js
console.log('初始内存使用情况:', process.memoryUsage());
const largeArray = new Array(1e6).fill('A');
setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log(`堆总量: ${memoryUsage.heapTotal}`);
}, 1000);
分配一个大型数组会增加堆总量。记录的堆总量显示了为 JavaScript 对象分配的内存。
➜ node heap.js
初始内存使用情况: {
rss: 38535168,
heapTotal: 4702208,
heapUsed: 2559224,
external: 1089863,
arrayBuffers: 10515
}
堆总量: 12976128
堆总量: 12976128
堆总量: 12976128
堆总量: 12976128
堆总量: 12976128
堆总量: 12976128
堆总量: 12976128
- 堆已使用: 对象实际使用的内存量。
堆已使用指的是当前由堆上的 JavaScript 对象使用的内存量。
当我们将对象推入数组时,我们正在增加堆使用的内存量。
// heap-used.js
console.log('初始内存使用情况:', process.memoryUsage());
let data = [];
for (let i = 0; i < 1e6; i++) {
data.push({ index: i });
}
setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log(`堆已使用: ${memoryUsage.heapUsed}`);
}, 1000);
随着添加更多对象,堆已使用的值将会上升。
➜ node heap-used.js
初始内存使用情况: {
rss: 38748160,
heapTotal: 4702208,
heapUsed: 2559424,
external: 1089863,
arrayBuffers: 10515
}
堆已使用: 2833808
堆已使用: 2847776
堆已使用: 2850800
堆已使用: 2854352
堆已使用: 2875800
堆已使用: 2879488
- 外部: 绑定到 JavaScript 的 C++ 对象使用的内存。
外部内存指的是通过绑定创建的、与 JavaScript 相关联的 C++ 对象所使用的内存。这些对象是通过绑定创建的,允许 JavaScript 与原生代码交互,在典型的 JavaScript 堆之外分配内存。
这部分内存在 JavaScript 中不直接可见,但仍然增加了应用程序所使用的总内存。
Buffer.alloc 方法分配了一个 50MB 的缓冲区,被追踪为外部内存。
// external.js
const buffer = Buffer.alloc(50 * 1024 * 1024); // 分配 50MB 的缓冲区
console.log('初始内存使用情况:', process.memoryUsage());
setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log(`外部内存: ${memoryUsage.external}`);
}, 1000);
这个例子记录了外部内存的使用情况,将反映缓冲区的分配。
➜ node external.js
初始内存使用情况: {
rss: 39223296,
heapTotal: 4702208,
heapUsed: 2560832,
external: 53518663,
arrayBuffers: 52439315
}
外部内存: 53814435
外部内存: 53814435
外部内存: 53814435
外部内存: 53814435
外部内存: 53814435
外部内存: 53814435
外部内存: 53814435
- 数组缓冲区: 为 ArrayBuffer 对象分配的内存。
数组缓冲区是用于 ArrayBuffer 对象的内存。这些对象在 JavaScript 中存储固定长度的二进制数据。
ArrayBuffer 是 JavaScript 类型数组系统的一部分,允许您直接使用二进制数据。
这些缓冲区的内存与常规 JavaScript 对象的内存分开跟踪。它们通常用于处理原始数据,如文件或网络协议。
以下是一个示例,我在这里分配了一个 50MB 的 ArrayBuffer,然后检查我的 Node.js 进程的初始内存使用情况。
// array-buffer.js
const buffer = new ArrayBuffer(50 * 1024 * 1024); // 50MB ArrayBuffer
console.log('初始内存使用情况:', process.memoryUsage());
setInterval(() => {
const memoryUsage = process.memoryUsage();
console.log(`数组缓冲区: ${memoryUsage.arrayBuffers}`);
}, 1000);
➜ node array-buffer.js
初始内存使用情况: {
rss: 39075840,
heapTotal: 4702208,
heapUsed: 2559496,
external: 53518663,
arrayBuffers: 52439315
}
数组缓冲区: 52439315
数组缓冲区: 52439315
数组缓冲区: 52439315
数组缓冲区: 52439315
数组缓冲区: 52439315
数组缓冲区: 52439315
常见 JavaScript 内存泄漏的原因
JavaScript 中的内存泄漏通常源于:
- 变量管理不当
管理不当的变量可能导致内存泄漏。
例如,如果您声明了应该临时的变量,但忘记清理它们,它们将继续占用内存。
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 的全局对象中。如果当这些数据不再需要时没有被移除,它将不必要地继续使用内存。
如果这些变量存储在全局作用域中,这尤其成问题,因为它们会在应用程序的整个生命周期中持续存在。
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 对象将在整个应用程序生命周期中持续存在
globalUserSessions 是一个全局对象,用于存储用户会话数据。因为它在全局作用域中,所以它在整个应用程序运行时都存在。
如果会话没有使用 removeUserSession 正确移除,数据将无限期地保留在内存中,导致内存泄漏。
- 持久的全局对象
全局对象可以比需要的时间长地保留内存。它们中的数据在不再需要后仍可能保留在内存中。这会逐渐增加内存使用。
global.config = {
settings: new Array(1000000).fill('Configuration')
};
// 内存泄漏:'config' 是全局的,将在整个应用程序生命周期中保留在内存中
由于 config 是全局可访问的并且从未被清除,它使用的内存将被保留在整个应用程序的运行期间。这里有一种我们可以避免内存泄漏的方法:
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 在函数运行后被清除,为垃圾回收释放了内存。
- 未移除的事件监听器
如果添加了事件监听器并且在不再需要时没有正确移除,可能会导致内存泄漏。
每个事件监听器都保留了对函数及其使用的任何变量的引用,阻止垃圾回收器回收该内存。
随着时间的推移,如果你不断添加监听器而没有移除它们,这将导致内存使用增加。
以下示例演示了如果事件监听器没有被正确移除,如何导致内存泄漏:
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
function listener() {
console.log('事件触发了!');
}
// 重复添加事件监听器
setInterval(() => {
myEmitter.on('event', listener);
}, 1000);
每秒添加一个新的事件监听器。然而,这些监听器从未被移除,导致它们在内存中累积。
每个监听器都持有对 listener 函数及其相关联的任何变量的引用,阻止垃圾回收,并导致内存使用随时间增加。
为了防止这种内存泄漏,当事件监听器不再需要时,你应该移除它们。
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
function listener() {
console.log('事件触发了!');
}
// 添加一个事件监听器
myEmitter.on('event', listener);
// 触发事件然后移除监听器
myEmitter.emit('event');
myEmitter.removeListener('event', listener);
// 或者,你可以使用 `once` 方法添加一个在触发后自动移除自身的监听器
myEmitter.once('event', listener);
- 捕获变量的闭包
JavaScript 中的闭包可能会无意中比需要的时间长地保留变量。当闭包捕获一个变量时,它会保留对该内存的引用。
如果闭包在长时间运行的过程中使用或没有正确终止,捕获的变量将保留在内存中,导致泄漏。
function createClosure() {
let capturedVar = new Array(1000000).fill('Data');
return function() {
console.log(capturedVar[0]);
};
}
const closure = createClosure();
// 即使不再使用,闭包也持有 'capturedVar'
为了避免泄漏,请确保闭包不会不必要地捕获大变量或在不再需要时结束它们。
function createClosure() {
let capturedVar = new Array(1000000).fill('Data');
return function() {
console.log(capturedVar[0]);
capturedVar = null; // 当不再需要时释放内存
};
}
const closure = createClosure();
closure(); // 使用后释放 'capturedVar'
- 未管理的回调
在某些情况下,如果回调持有比必要时间更长的变量或对象的引用,可能会导致内存问题。
然而,JavaScript 的垃圾回收器通常在引用不再需要时有效地清理内存。
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 函数分配了一个大数组(data),其中包含 100 万个元素。
- 回调引用: 回调函数(handleData)在 1 秒后被 setTimeout 调用时引用了这个大数组。
尽管分配了大量的内存,JavaScript 的垃圾回收器确保在不再需要时释放内存。
没有必要手动清除引用,除非你正在处理引用无意中被保留的非常复杂的情况。
避免不必要的复杂性
在大多数情况下,不需要在标准的异步回调中手动清除引用。
过于复杂(不推荐)
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 关键字设置为特定值。如果你不小心,这可能会导致内存泄漏。
function MyClass() {
this.largeData = new Array(1000000).fill('leak');
window.addEventListener('click', this.handleClick.bind(this));
}
MyClass.prototype.handleClick = function() {
console.log('点击了');
};
// 如果 MyClass 实例被销毁,但事件监听器没有被移除,
// 绑定的函数将保持实例在内存中的活动状态。
为什么 bind() 会导致内存泄漏
1. 引用被保留: 当你使用 bind() 时,新函数会记住原始函数和 this 值。如果你在不再需要时没有移除函数,它就会持续存在并占用内存。
2. 大对象保留在内存中: 绑定的函数可能会意外地使大对象保留在内存中,即使你不再需要它们。
- 循环引用
当两个对象相互引用时,就会发生循环引用。这会创建一个循环,可能会使垃圾回收器感到困惑,阻止它释放内存。
function CircularReference() {
this.reference = this; // 循环引用
}
let obj = new CircularReference();
obj = null; // 将 obj 设置为 null 可能不会释放内存。
即使你将 obj 设置为 null,由于自循环,内存可能不会被释放。
如何避免循环引用
- 打破循环: 确保对象在不再需要时不要相互引用。这有助于垃圾回收器清除它们。
function CircularReference() {
this.reference = this;
}
let obj = new CircularReference();
// 打破循环引用
obj.reference = null;
obj = null; // 现在内存可以被释放了
通过将 obj.reference 设置为 null,我们打破了循环引用。这允许垃圾回收器在 obj 不再被需要时释放内存。
- 使用弱引用: 使用 WeakMap、WeakSet 或 WeakRef 允许垃圾回收器即使存在引用也能清理内存,只要它们是弱的。
let weakMap = new WeakMap();
function CircularReference() {
let obj = {};
weakMap.set(obj, "这是一个弱引用");
return obj;
}
let obj = CircularReference();
// 当不再需要时,对象可以被垃圾回收
weakMap 持有对 obj 的弱引用。这意味着当 obj 在其他地方不再被使用时,即使它在 weakMap 中被引用,它仍然可以被垃圾回收。
let weakRef;
function createObject() {
let obj = { data: '重要' };
weakRef = new WeakRef(obj);
return obj;
}
let obj = createObject();
console.log(weakRef.deref()); // { data: '重要' }
obj = null; // 现在对象可以被垃圾回收了
weakRef 允许你对 obj 持有一个弱引用。如果 obj 被设置为 null 并且没有其他引用它,即使 weakRef 仍然存在,它也可以被垃圾回收。
快速说明
WeakMap、WeakSet 和 WeakRef 有助于防止内存泄漏,但你可能并不总是需要它们。它们更多用于高级用例,如管理缓存或大型数据。
如果你正在处理典型的 web 应用程序,你可能不会经常看到它们,但知道它们存在并在需要时可以使用是很好的。
在 Node.js 中分析内存使用情况
要找到内存泄漏,你需要分析你的应用程序以了解内存是如何被使用的。
以下是一个 Node.js 应用程序,旨在模拟 CPU 密集型任务、I/O 操作,并有意为测试目的创建内存泄漏。
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('内存泄漏'));
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 个请求来模拟 CPU、I/O 和内存泄漏。
#!/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 "向 $CPU_INTENSIVE_URL 和 $SIMULATE_IO_URL 发送 $REQUESTS 个请求..."
# 循环 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 "完成。"
它循环遍历 URL 并使用 curl 发送静默请求,将它们在后台运行以模拟高负载。
➜ ./load_test.sh
向 http://localhost:3000/cpu-intensive 和 http://localhost:3000/simulate-io 和 http://localhost:3000/cause-memory-leak 发送 100 个请求
完成。
以下是我们的服务器对压力测试的响应。在开始测试之前,请确保服务器正在运行。
➜ node --prof server.js
服务器正在端口 3000 上运行
内存使用情况(模拟 I/O): {
rss: 122863616,
heapTotal: 17547264,
heapUsed: 8668016,
external: 54075004,
arrayBuffers: 52439275
}
内存泄漏数组长度: 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
内存使用情况(CPU 任务): {
rss: 122716160,
heapTotal: 17547264,
heapUsed: 11393456,
external: 54075004,
arrayBuffers: 52439275
}
内存泄漏数组长度: 173
分析结果
分析数据将被保存在一个名为 isolate-0xXXXXXXXXXXXX-v8.log 的文件中。
要处理日志并获得人类可读的摘要,请运行:
➜ node --prof-process isolate-0x140008000-42065-v8.log > processed-profile.txt
这将生成一个包含 CPU 分析数据的 processed-profile.txt 文件,其中包括应用程序花费时间的详细信息以及它如何管理内存。
打开 processed-profile.txt 文件,查找应用程序使用大量时间或内存的区域。
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 应用程序高效和可靠的关键。