javascript内存泄漏调试工具mac_node.js 内存泄漏的秘密

aecbdf365f86d75e7f9b23750c959711.png

一直以来,跟踪 Node.js 的内存泄漏是一个反复出现的话题,人们始终希望对其复杂性和原因了解更多。

并非所有的内存泄漏都显而易见。但是,一旦我们确定了其模式,就必须在内存使用率,内存中保存的对象和响应时间之间寻找关联。在检查对象时,应该根据自己所用的框架或技术(例如服务器端渲染),研究收集了多少对象,以及它们是否正常。希望在完成本文结束之后,你将能够理解并寻找一种策略来调试 Node.js 程序的内存消耗。

Node.js 中的垃圾回收机制

JavaScript 是一种垃圾回收语言,而 Google 的 V8 最初是为 Google Chrome 创建的JavaScript引擎,在许多情况下都可以用作独立的运行时。Node.js 中垃圾收集器的两个重要操作是:

  1. 确定有用的或无用的对象,并且
  2. 回收或重用无用对象所占用的内存。

需要记住的要点:在垃圾回收器运行时,它将完全暂停你的程序,直到完成工作为止。因此,你需要通过维护对象的引用来最大程度地减少其工作。

V8 JavaScript 引擎会自动分配和取消分配 Node.js 进程使用的所有内存。让我们看看实际情况是怎样的。

如果你将内存视为一个树结构,那么可以想象 V8 从“根节点”开始保存程序中所有的变量。这可能是你的 window 对象,也可能是 Node.js 模块中的全局对象,通常称为控制者。需要牢记的一点是,你无法对怎样取消分配“根”节点进行控制。

b408d7716d992b1cf666075c3c1edbc1.png

接下来,你将找到一个 Object 节点,通常被称为叶子(没有子引用的节点)。最后 JavaScript 中有 4 种数据类型:布尔值,字符串,数字和对象。

V8 将遍历该树并尝试识别无法从“根”节点访问的数据组。如果无法从“根”节点访问该数据,则 V8 假定不再使用该数据,并释放内存。请记住:要确定某个对象是否处于活动状态,需要检查是否可通过被定义为活动对象的某个指针链到达;其他所有的情况,例如无法从根节点访问,或无法被根节点或另一个活动对象引用的对象,都会被视为垃圾。

简而言之,垃圾收集器有两个主要任务:

  1. 跟踪
  2. 计算对象之间的引用。

当你需要跟踪来自另一个进程的远程引用时,它可能会变得很棘手,但是在 Node.js 程序中,我们通常用单进程,这样使我们更加轻松。

V8 的内存方案

V8 使用类似于 Java 虚拟机的方案,并将内存划分为多个段。实现这种包装方案的东西被称为“驻留集”,它是指在 RAM 中驻留的进程所占用的内存部分。

在驻留集中,你会发现:

  • 代码段:代码实际执行的位置。
  • 栈: 包含局部变量和所有值类型,其指针引用堆上的对象或定义程序的控制流。
  • 堆: 专门用于存储引用类型(如对象、字符串和闭包)的内存段。

2d3fc5c6a12851709d2f584730a5b967.png

还有重要的两点要记住:

  • 对象的浅大小:保存对象本身所需的内存大小
  • 对象的保留大小:当删除对象及其依赖对象时,被释放的内存大小

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; charset=utf-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应用程序:

fbee104e2b145a35b6f818d426153161.png

我们一直在使用 3S(3 Snapshot)方法进行诊断并确定可能的内存问题。有趣的是,我们发现这是 Gmail 团队的 Loreena Lee 长期使用的一种解决内存问题的方法。此方法的步骤:

  1. 打开 Chrome DevTools 并访问 chrome://inspect
  2. 在底部的“Remote Target”中,单击 inspect 按钮。

7529b6de7d8deaf4148b875651ed4c3c.png

注意: 要确保已将 Inspector 附加到要分析的 Node.js 程序。你还可以用 ndb 连接到 Chrome DevTools。

当应用运行时,你将在控制台的输出中看到一条 Debugger Connected 消息。

  1. 转到 Chrome DevTools > Memory
  2. 获取堆快照

c2331d45e65c964cea47b300c2a49ce9.png

在这种情况下,我们得到了第一个快照,而服务没有进行任何负载或处理。这是针对某些用例的提示:如果我们能够确定在接受请求或进行某些处理之前不需要对程序进行任何预热,那就很好了。有时,在获取第一个堆快照之前先进行热身操作是有意义的,因为在某些情况下,你可能会在第一次调用时对全局变量进行了延迟初始化。

  1. 在你的程序中执行你认为导致内存泄漏的操作。

在这种情况下,我们将运行 npm run load-mem。这将启动 ab 来模拟 Node.js 应用程序中的流量或负载。

02fe26a2410d2944bfaccc1d40c287b8.png

得到堆快照

25e1112466301ea6517131754f14b5dc.png
  1. 再次在你的程序中执行你认为会导致内存泄漏的操作。
  2. 获取最终的堆快照

c54c9c2094ff68650199b668fd719e1e.png
  1. 选择最新得到的快照。
  2. 在窗口顶部,找到显示 “All objects” 的下拉列表,并将其切换为“Objects allocated between snapshots 1 and 2”。(如果需要,你也可以对 2 和 3 执行相同的操作)。这将大大减少你看到的对象数量。

e3948c1f275268e58bea52314fe599ff.png

比较视图也可以帮你识别那些对象:

bb29d170f710c59156b387a745c60e28.png

在该视图中,你将看到泄漏对象的列表:顶级条目(每个构造函数一行)、对象到GC根的距离、对象实例数、浅大小和保留大小。你可以通过选择一行来查看其内容。一个好的经验法则是,首先忽略括号中的项目,因为它们是内置结构。@ 字符是对象的唯一 ID,可让你比较每个对象的堆快照。

典型的内存泄漏可能是通过意外地将对对象的引用存储在无法进行垃圾回收的全局对象中,从而保留了预期仅在一个请求周期内持续存在的对象的引用。

这个例子故意留下了一个内存泄漏的问题,在请求一个从 API 查询返回的对象时生成带有日期时间戳的随机对象,并将其存储在全局数组中来泄漏该对象。通过查看几个保留的对象,你会看到一些泄漏数据的示例,可用于跟踪应用程序中的泄漏。

NSolid 非常适合这种类型的用例,因为它可以使你很好地了解在执行的每个任务或负载测试中内存是怎样增加的。如果你感到好奇,还可以实时查看每个性能分析动作如何影响 CPU。

012ccb98e414692c817fc37adfa25dd3.png

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-memwatch,node-inspector 或 v8-profiler)来检查内存。你需要的一切都已经集成在了 Node.js 的二进制文件中(尤其是 node.js 检查器和调试器)。如果你需要更专业的工具,则可以使用 NSolid、Chrome DevTools 或其他知名软件。
  • 考虑在何时何地触发堆快照和 CPU profile。由于要在生产环境中进行快照,你将会希望同时触发这两者(主要是在测试中),所以这会需要大量的 CPU 操作。另外,在关闭进程和进行冷重启之前,请确认有多少堆转储被写入了。
  • 没有哪一种工具可以解决所有问题。要根据程序的具体情况进行测试、测量、判断和解决。选择适合你体系结构的最佳工具,并选择一种可以提供更多有用数据来帮你解决问题的工具。

原文:https://nodesource.com/blog/memory-leaks-demystifi

欢迎关注前端公众号:前端先锋,免费领取 Vue、React 性能优化教程。

0cfc02df91dde2f620c259bf1187175d.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/306715.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++STL中的find()函数 有两种使用方法

cSTL中的find()函数 有两种使用方法 方法一&#xff1a; 开头引头文件&#xff1a;中的函数 其调用形式为 find&#xff08;start,end,value&#xff09; start搜寻的起点&#xff0c;end搜寻的终点&#xff0c;要寻找的value值; 如果没有找到&#xff0c;则返回end。函数的返…

关于C# Span的一些实践

Span这个东西出来很久了&#xff0c;居然因为5.0又火起来了。特别感谢RC兄弟提出这个话题。相关知识在大多数情况下&#xff0c;C#开发时&#xff0c;我们只使用托管内存。而实际上&#xff0c;C#为我们提供了三种类型的内存&#xff1a;堆栈内存 - 最快速的内存&#xff0c;能…

问题 C: 【例2-3】围圈报数

题目描述 有&#xff4e;(n<100)个人依次围成一圈&#xff0c;从第&#xff11;个人开始报数&#xff0c;数到第&#xff4d;个人出列&#xff0c;然后从出列的下一个人开始报数&#xff0c;数到第&#xff4d;个人又出列&#xff0c;…&#xff0c;如此反复到所有的人全部…

怎样用python批量处理文件夹_python批量处理文件或文件夹

本文实例为大家分享了python批量处理文件或文件夹的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下 # -*- coding: utf-8 -*- import os,shutil import sys import numpy as np ##########批量删除不同文件夹下的同名文件夹############# def arrange_file(dir_path0…

leetcode-349-两个数组的交集

给定两个数组&#xff0c;编写一个函数来计算它们的交集。 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,2,1], nums2 [2,2] 输出&#xff1a;[2] 示例 2&#xff1a; 输入&#xff1a;nums1 [4,9,5], nums2 [9,4,9,8,4] 输出&#xff1a;[9,4] 说明&#xff1a; 输…

Leansoft再发招贤令:面试官徐磊有话讲 | IDCF

&#xff08;图片来源于网络&#xff09;2020是Leansoft成立的第五年&#xff0c;凭借专业的服务及实施能力&#xff0c;逐渐成长为国内唯一的端到端专业DevOps实施服务公司。Leansoft是一家怎样的公司呢&#xff1f;准确地说&#xff0c;我们其实是国内唯一一家提供端到端的De…

问题 B: 数塔问题

题目描述 有如下所示的数塔&#xff0c;要求从顶层走到底层&#xff0c;若每一步只能走到相邻的结点&#xff0c;则经过的结点的数字之和最大是多少&#xff1f; 输入 第一行是一个整数N(1 < N < 20)&#xff0c;表示数塔的高度&#xff0c;接下来用N个数字表示数塔&a…

leetcode-345-翻转字符串中的元音字母

编写一个函数&#xff0c;以字符串作为输入&#xff0c;反转该字符串中的元音字母。 示例 1&#xff1a; 输入&#xff1a;“hello” 输出&#xff1a;“holle” 示例 2&#xff1a; 输入&#xff1a;“leetcode” 输出&#xff1a;“leotcede” 来源&#xff1a;力扣&…

e盾服务端源码_gRPC服务注册发现及负载均衡的实现方案与源码解析

今天聊一下gRPC的服务发现和负载均衡原理相关的话题&#xff0c;不同于Nginx、Lvs或者F5这些服务端的负载均衡策略&#xff0c;gRPC采用的是客户端实现的负载均衡。什么意思呢&#xff0c;对于使用服务端负载均衡的系统&#xff0c;客户端会首先访问负载均衡的域名/IP&#xff…

堆问题(最小堆变最大堆,堆删除,中序遍历)

2-6 设最小堆&#xff08;小根堆&#xff09;的层序遍历结果为 {8, 38, 25, 58, 52, 82, 70, 60}。用线性时间复杂度的算法将该堆调整为最大堆&#xff08;大根堆&#xff09;&#xff0c;然后连续执行两次删除最大元素操作&#xff08;DeleteMax&#xff09;。则该树的中序遍历…

推荐一款.NET Core开源爬虫神器:DotnetSpider

没有爬虫就没有互联网&#xff01;爬虫的意义在于采集大批量数据&#xff0c;然后基于此进行加工/分析&#xff0c;做更有意义的事情。谷歌&#xff0c;百度&#xff0c;今日头条&#xff0c;天眼查都离不开爬虫。去开源中国和Github查询C#的爬虫项目&#xff0c;仅有几个非常简…

Excel学习使用教程

1.Excel的保存与加密 加密&#xff1a; 我设置的密码&#xff1a;517485

python数据导入hive_Python操作HIve,将数据插入到Mysql

Python操作HIve&#xff0c;将数据插入到Mysql import sys from hive_service import ThriftHive from hive_service.ttypes import HiveServerException from thrift import Thrift from thrift.transport import TSocket from thrift.transport import TTransport from thrif…

问题 D: 二叉树求高度

题目描述 已知一棵二叉树用邻接表结构存储&#xff0c;求这棵树的高度。例&#xff1a;如图二叉树的数据文件的数据格式如下: 输入 第一行n为二叉树的结点个树&#xff0c;n≤100&#xff1b;以下第一列数据是各结点的值&#xff0c;第二列数据是左儿子结点编号&#xff0c;第…

.Net Core in Docker - 使用阿里云Codepipeline及阿里云容器镜像服务实现持续集成(CI)...

前面已经介绍过了 .Net Core In Docker 在容器内编译并发布的内容。但是每次通过 SSH 链接到服务器敲命令&#xff0c;运行脚本也是挺麻烦的一件事。程序员是最懒的&#xff0c;能让电脑解决的问题绝不手动解决&#xff0c;如果当我们push一次代码后自动build代码&#xff0c;自…

leetcode-445. 两数相加 II

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数字都不会以零开头。 进阶&#xff1a; 如果输入链表不能修改该如何处理&#xff1f;换…

mysql 序列_MySql中序列的应用和总结

Mysql中的序列主要用于主键&#xff0c;主键是递增的字段&#xff0c;不可重复。Mysql与Oracle不同的是&#xff0c;它不支持原生态的sequence&#xff0c;需要用表和函数的组合来实现类似序列的功能。1.首先创建序列的主表/*2.其次创建如下三个函数&#xff0c;它们的功能分别…

汉诺塔问题详细解析zufeoj

汉诺塔&#xff08;Tower of Hanoi&#xff09;&#xff0c;又称河内塔&#xff0c;是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子&#xff0c;在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重…

靠刷算法题,真的可以刷进大厂吗?

我一直不知道我在大家心目中的定位是什么&#xff0c;但我内心其实是把自己定义为一个『工具人』的。可能是因为我自己本身就是程序员&#xff0c;所以更能理解程序员的不易吧。所以&#xff0c;我尽量不写水文&#xff0c;只分享干货。就是希望大家看了能够有所收获&#xff0…

PTA 7-3 地铁一日游 (30 分)

森森喜欢坐地铁。这个假期&#xff0c;他终于来到了传说中的地铁之城——魔都&#xff0c;打算好好过一把坐地铁的瘾&#xff01; 魔都地铁的计价规则是&#xff1a;起步价 2 元&#xff0c;出发站与到达站的最短距离&#xff08;即计费距离&#xff09;每 K 公里增加 1 元车费…