美妆网站设计,wordpress漫画采集,大学生创新项目申报书 做网站,孵化器网站建设方案一直以来#xff0c;跟踪 Node.js 的内存泄漏是一个反复出现的话题#xff0c;人们始终希望对其复杂性和原因了解更多。并非所有的内存泄漏都显而易见。但是#xff0c;一旦我们确定了其模式#xff0c;就必须在内存使用率#xff0c;内存中保存的对象和响应时间之间寻找关…一直以来跟踪 Node.js 的内存泄漏是一个反复出现的话题人们始终希望对其复杂性和原因了解更多。并非所有的内存泄漏都显而易见。但是一旦我们确定了其模式就必须在内存使用率内存中保存的对象和响应时间之间寻找关联。在检查对象时应该根据自己所用的框架或技术例如服务器端渲染研究收集了多少对象以及它们是否正常。希望在完成本文结束之后你将能够理解并寻找一种策略来调试 Node.js 程序的内存消耗。Node.js 中的垃圾回收机制JavaScript 是一种垃圾回收语言而 Google 的 V8 最初是为 Google Chrome 创建的JavaScript引擎在许多情况下都可以用作独立的运行时。Node.js 中垃圾收集器的两个重要操作是确定有用的或无用的对象并且回收或重用无用对象所占用的内存。需要记住的要点在垃圾回收器运行时它将完全暂停你的程序直到完成工作为止。因此你需要通过维护对象的引用来最大程度地减少其工作。V8 JavaScript 引擎会自动分配和取消分配 Node.js 进程使用的所有内存。让我们看看实际情况是怎样的。如果你将内存视为一个树结构那么可以想象 V8 从“根节点”开始保存程序中所有的变量。这可能是你的 window 对象也可能是 Node.js 模块中的全局对象通常称为控制者。需要牢记的一点是你无法对怎样取消分配“根”节点进行控制。接下来你将找到一个 Object 节点通常被称为叶子没有子引用的节点。最后 JavaScript 中有 4 种数据类型布尔值字符串数字和对象。V8 将遍历该树并尝试识别无法从“根”节点访问的数据组。如果无法从“根”节点访问该数据则 V8 假定不再使用该数据并释放内存。请记住要确定某个对象是否处于活动状态需要检查是否可通过被定义为活动对象的某个指针链到达其他所有的情况例如无法从根节点访问或无法被根节点或另一个活动对象引用的对象都会被视为垃圾。简而言之垃圾收集器有两个主要任务跟踪计算对象之间的引用。当你需要跟踪来自另一个进程的远程引用时它可能会变得很棘手但是在 Node.js 程序中我们通常用单进程这样使我们更加轻松。V8 的内存方案V8 使用类似于 Java 虚拟机的方案并将内存划分为多个段。实现这种包装方案的东西被称为“驻留集”它是指在 RAM 中驻留的进程所占用的内存部分。在驻留集中你会发现代码段代码实际执行的位置。栈 包含局部变量和所有值类型其指针引用堆上的对象或定义程序的控制流。堆 专门用于存储引用类型如对象、字符串和闭包的内存段。还有重要的两点要记住对象的浅大小保存对象本身所需的内存大小对象的保留大小当删除对象及其依赖对象时被释放的内存大小Node.js 有一个对象以字节为单位描述 Node.js 进程的内存使用情况。在对象内部你会发现rss 是指驻留集大小。heapTotal 和 heapUsed 是指 V8 的内存使用情况。external 是指与 V8 所管理的 JavaScript 对象绑定的 C 对象的内存使用情况。查找泄漏Chrome DevTools 是一个很棒的工具可用于通过远程调试来诊断 Node.js 程序中的内存泄漏。也有其他为你提供类似功能的工具。但是你需要记住概要分析是一项繁重的 CPU 任务可能会对你的程序产生负面影响一定要注意这一点我们将要介绍的 Node.js 程序是一个简单的 HTTP API Server它具有多个端点向使用该服务的人返回不同的信息。你可以克隆这个程序的repository。 1const http require(http)23const leak []45function requestListener(req, res) {67 if (req.url /now) {8 let resp JSON.stringify({ now: new Date() })9 leak.push(JSON.parse(resp))
10 res.writeHead(200, { Content-Type: application/json })
11 res.write(resp)
12 res.end()
13 } else if (req.url /getSushi) {
14 function importantMath() {
15 let endTime Date.now() (5 * 1000);
16 while (Date.now() endTime) {
17 Math.random();
18 }
19 }
20
21 function theSushiTable() {
22 return new Promise(resolve {
23 resolve( );
24 });
25 }
26
27 async function getSushi() {
28 let sushi await theSushiTable();
29 res.writeHead(200, { Content-Type: text/html; charsetutf-8 })
30 res.write(Enjoy! ${sushi});
31 res.end()
32 }
33
34 getSushi()
35 importantMath()
36 } else {
37 res.end(Invalid request)
38 }
39}
40
41const server http.createServer(requestListener)
42server.listen(process.env.PORT || 3000)
启动Node.js应用程序我们一直在使用 3S3 Snapshot方法进行诊断并确定可能的内存问题。有趣的是我们发现这是 Gmail 团队的 Loreena Lee 长期使用的一种解决内存问题的方法。此方法的步骤打开 Chrome DevTools 并访问 chrome://inspect。在底部的“Remote Target”中单击 inspect 按钮。注意 要确保已将 Inspector 附加到要分析的 Node.js 程序。你还可以用 ndb 连接到 Chrome DevTools。当应用运行时你将在控制台的输出中看到一条 Debugger Connected 消息。转到 Chrome DevTools Memory获取堆快照在这种情况下我们得到了第一个快照而服务没有进行任何负载或处理。这是针对某些用例的提示如果我们能够确定在接受请求或进行某些处理之前不需要对程序进行任何预热那就很好了。有时在获取第一个堆快照之前先进行热身操作是有意义的因为在某些情况下你可能会在第一次调用时对全局变量进行了延迟初始化。在你的程序中执行你认为导致内存泄漏的操作。在这种情况下我们将运行 npm run load-mem。这将启动 ab 来模拟 Node.js 应用程序中的流量或负载。得到堆快照再次在你的程序中执行你认为会导致内存泄漏的操作。获取最终的堆快照选择最新得到的快照。在窗口顶部找到显示 “All objects” 的下拉列表并将其切换为“Objects allocated between snapshots 1 and 2”。如果需要你也可以对 2 和 3 执行相同的操作。这将大大减少你看到的对象数量。比较视图也可以帮你识别那些对象在该视图中你将看到泄漏对象的列表顶级条目每个构造函数一行、对象到GC根的距离、对象实例数、浅大小和保留大小。你可以通过选择一行来查看其内容。一个好的经验法则是首先忽略括号中的项目因为它们是内置结构。 字符是对象的唯一 ID可让你比较每个对象的堆快照。典型的内存泄漏可能是通过意外地将对对象的引用存储在无法进行垃圾回收的全局对象中从而保留了预期仅在一个请求周期内持续存在的对象的引用。这个例子故意留下了一个内存泄漏的问题在请求一个从 API 查询返回的对象时生成带有日期时间戳的随机对象并将其存储在全局数组中来泄漏该对象。通过查看几个保留的对象你会看到一些泄漏数据的示例可用于跟踪应用程序中的泄漏。NSolid 非常适合这种类型的用例因为它可以使你很好地了解在执行的每个任务或负载测试中内存是怎样增加的。如果你感到好奇还可以实时查看每个性能分析动作如何影响 CPU。demo在实际项目中你不可能总是盯着用于监视程序的工具。NSolid 的一大优点是可以为应用程序的不同指标设置阈值和限制。例如你可以将 NSolid 设置为在使用的内存量超过 X 时或者在 X 时间内尚未从高消耗高峰恢复内存的情况下进行堆快照。听起来不错吧标记和清理V8 的垃圾收集器主要基于 Mark-Sweep 收集算法该算法包括跟踪垃圾收集该操作通过标记可达的对象然后清理内存并回收未标记的对象必须无法访问将其纳入释放列表。这也称为世代垃圾收集器对象可以在新声代、从新生代到老生代、以及老生代中移动。移动对象的代价非常打因为需要将对象的基础内存复制到新位置并且指向这些对象的指针也需要更新。用人话解释V8 递归查找所有对象到“根”节点的引用路径。例如在 JavaScript 中“window” 对象是可以充当 Root 的全局变量的示例。window 对象始终存在因此垃圾收集器可以认为它及其所有子对象始终存在即不是垃圾。如果有任何引用则没有指向“根”节点的路径。特别是当它以递归方式查找未引用的对象时将被标记为垃圾稍后将会被清除以释放该内存并将其返回给操作系统。但是现代的垃圾收集器以不同的方式对这种算法进行了改进但本质是相同的可访问的内存被标记为一类其余的被视为垃圾。请记住从根可以访问到的所有内容均不视为垃圾。不需要的引用是保留在代码中某个位置的变量这些变量将不再使用并且指向可以释放的内存因此要了解 JavaScript 中最常见的泄漏我们需要了解通常忘记引用的方式。Orinoco 垃圾收集器Orinoco 是最新 GC 项目的代号它利用最新的增量和并发技术进行垃圾回收并有释放主线程的功能。描述 Orinoco 性能的重要指标之一是垃圾回收器执行时主线程暂停的频率和时间。对于经典的“世界末日”收集者而言这些时间间隔会因为延迟、质量差的渲染以及响应时间的增加而影响程序的用户体验。V8 在新声代内存中的辅助流之间分配垃圾回收工作清除。每个流接收一组指针然后将所有活动对象移动到“to-space”。将对象移至“to-space”时线程需要通过读、写、比较和交换的原子操作进行同步以避免出现另一个线程找到相同的对象但遵循不同路径并尝试移动的情况。引用自 V8 官网在现有 GC 中添加并行、增量和并发技术是一项多年的努力但已取得了回报将大量工作移交给了后台任务。它大大改善了暂停时间、延迟和页面加载使动画、滚动和用户交互更加顺畅。并行的 Scavenger 根据工作量将主线程新声代垃圾收集的总时间减少了大约 20–50。Idle-time GC 可以在 Gmail 空闲时将其 JavaScript 堆内存减少 45。并发标记和清除可以将笨重的 WebGL 游戏中的暂停时间减少多达 50。Mark-Evacuate 收集器包括三个阶段标记、复制和更新指针。为了避免在新声代中清理页面以维护空闲列表仍然使用 semi-space 来维护新生代它始终保持紧凑状态即在垃圾回收期间将活动对象复制到 “to-space” 中。并行进行的好处是可以获得“exact liveness”信息。通过仅移动和重新链接主要包含活动对象的页面可以用此信息来避免复制这也可以由完整的 Mark-Sweep-Compact 收集器执行。它通过和标记清除算法相同的方式标记堆中的活动对象来工作这意味着堆通常会被碎片化。V8 当前随附有并行的 Scavenger可在大量基准测试中减少主线程新生代垃圾回收约 20–50 的总时间。与暂停主线程、响应时间和页面加载有关的所有方面都得到了显着改善这使得页面上的动画、滚动和用户交互更加流畅。并行收集器可以将新内存的总处理时间减少 20–50具体取决于负载。但是工作还没有结束减少停顿仍然是一项重要任务我们将继续寻找使用更先进的技术来实现这一目标的可能性。总结大多数开发人员在开发 JavaScript 程序时无需考虑 GC但是了解一些内部知识可以帮助你考虑内存使用情况和有用的编程模式。例如考虑到 V8 中基于世代的堆结构从 GC 角度来说维护低生存期的对象的成本实际上是相当低的因为我们主要为存在的对象付出代价。这种模式不仅特定于 JavaScript而且对于许多支持垃圾回收的语言也都有效。要点请勿使用过时或不推荐的软件包例如node-memwatchnode-inspector 或 v8-profiler来检查内存。你需要的一切都已经集成在了 Node.js 的二进制文件中尤其是 node.js 检查器和调试器。如果你需要更专业的工具则可以使用 NSolid、Chrome DevTools 或其他知名软件。考虑在何时何地触发堆快照和 CPU profile。由于要在生产环境中进行快照你将会希望同时触发这两者主要是在测试中所以这会需要大量的 CPU 操作。另外在关闭进程和进行冷重启之前请确认有多少堆转储被写入了。没有哪一种工具可以解决所有问题。要根据程序的具体情况进行测试、测量、判断和解决。选择适合你体系结构的最佳工具并选择一种可以提供更多有用数据来帮你解决问题的工具。原文https://nodesource.com/blog/memory-leaks-demystifi欢迎关注前端公众号前端先锋免费领取 Vue、React 性能优化教程。