Node.js 应用的内存泄漏问题的检测方法-阿里云开发者社区

开发者社区> -技术小能手-> 正文

Node.js 应用的内存泄漏问题的检测方法

简介: Debugging Memory Leaks in Node.js Applications
+关注继续查看

Node.js 是一个基于 Chrome 的 V8 JavaScript 引擎构建的平台,用于轻松构建快速且可扩展的网络应用程序。


Google 的 V8 ——Node.js 背后的 JavaScript 引擎, 它的性能令人难以置信,并且 Node.js 在许多用例中运行良好的原因有很多,但您总是受到堆大小的限制。 当您需要在 Node.js 应用程序中处理更多请求时,您有两种选择:垂直扩展或者水平扩展。 水平扩展意味着您必须运行更多并发应用程序实例。 如果做得好,您最终能够满足更多请求。 垂直扩展意味着您必须提高应用程序的内存使用和性能或增加应用程序实例可用的资源。


Node.js Memory Leak Debugging Arsenal

MEMWATCH

如果您搜索“如何在 node.js 中查找泄漏”,您可能会找到的第一个工具是 memwatch。 原来的包早就废弃了,不再维护。 但是,您可以在 GitHub 的存储库分叉列表中轻松找到它的更新版本。 这个模块很有用,因为它可以在看到堆增长超过 5 次连续垃圾收集时发出泄漏事件。


HEAPDUMP

很棒的工具,它允许 Node.js 开发人员拍摄堆快照并在以后使用 Chrome 开发人员工具检查它们。


NODE-INSPECTOR

甚至是 heapdump 的更有用的替代方案,因为它允许您连接到正在运行的应用程序,进行堆转储,甚至可以即时调试和重新编译它。


Taking “node-inspector” for a Spin

不幸的是,您将无法连接到在 Heroku 上运行的生产应用程序,因为它不允许将信号发送到正在运行的进程。 然而,Heroku 并不是唯一的托管平台。


为了体验 node-inspector 的实际操作,我们将使用 restify 编写一个简单的 Node.js 应用程序,并在其中放置一些内存泄漏源。 这里所有的实验都是用 Node.js v0.12.7 进行的,它是针对 V8 v3.28.71.19 编译的。

image.png

image.png

当使用 –trace_gc 标志启动 Node.js 应用程序时,会打印这些日志行:


node --trace_gc app.js


让我们假设我们已经使用这个标志启动了我们的 Node.js 应用程序。 在将应用程序与节点检查器连接之前,我们需要将 SIGUSR1 信号发送给正在运行的进程。 如果您在集群中运行 Node.js,请确保您连接到从属进程之一。


kill -SIGUSR1 $pid # Replace $pid with the actual process ID


通过这样做,我们使 Node.js 应用程序(准确地说是 V8)进入调试模式。 在此模式下,应用程序会使用 V8 调试协议自动打开端口 5858。


我们的下一步是运行 node-inspector,它将连接到正在运行的应用程序的调试界面,并在端口 8080 上打开另一个 Web 界面。


$ node-inspector

Node Inspector v0.12.2

Visit http://127.0.0.1:8080/?ws=127.0.0.1:8080&port=5858 to start debugging.


如果应用程序在生产环境中运行并且您有防火墙,我们可以通过隧道将远程端口 8080 连接到本地主机:


ssh -L 8080:localhost:8080 admin@example.com


现在,您可以打开 Chrome 网络浏览器并完全访问附加到远程生产应用程序的 Chrome 开发工具。


Let’s Find a Leak!

V8 中的内存泄漏并不是我们从 C/C++ 应用程序中知道的真正的内存泄漏。 在 JavaScript 中,变量不会成为 void,它们只会被“遗忘”。 我们的目标是找到这些被开发人员遗忘的变量。


在 Chrome 开发者工具中,我们可以访问多个分析器。 我们对记录堆分配特别感兴趣,它会随着时间的推移运行并拍摄多个堆快照。 这让我们可以清楚地看到哪些对象正在泄漏。


开始记录堆分配,让我们使用 Apache Benchmark 在我们的主页上模拟 50 个并发用户。

image.png

image.png

image.png

image.png

V8堆分为几个不同的空间:


new space:这个空间比较小,大小在1MB到8MB之间。 大多数对象都在这里分配。

old pointer space:具有可能具有指向其他对象的指针的对象。 如果对象在新空间中存活的时间足够长,它就会被提升到旧指针空间。

old data space:仅包含原始数据,如字符串、装箱数字和未装箱双精度数组。 在新空间中在 GC 中存活足够长时间的对象也被移动到这里。

large object space:在此空间中创建太大而无法放入其他空间的对象。 每个对象在内存中都有自己的 mmap 区域

code space:包含由 JIT 编译器生成的汇编代码。

Cell space, property cell space, map space:该空间包含单元格、属性单元格和地图。 这用于简化垃圾收集。

每个空间由页面组成。页面是从操作系统使用 mmap 分配的内存区域。除了大对象空间中的页面外,每个页面的大小始终为 1MB。


V8 有两个内置的垃圾收集机制:Scavenge、Mark-Sweep 和 Mark-Compact。


Scavenge 是一种非常快速的垃圾收集技术,可以处理 New Space 中的对象。 Scavenge 是切尼算法的实现。这个想法很简单,New Space 被分成两个相等的半空间:To-Space 和 From-Space。当 To-Space 已满时,会发生 Scavenge GC。它只是交换 To 和 From 空间并将所有活动对象复制到 To-Space 或将它们提升到旧空间之一,如果它们在两次清除中幸存下来,然后从空间中完全删除。清理速度非常快,但是它们具有保持双倍大小的堆和不断在内存中复制对象的开销。使用清除的原因是因为大多数对象都很年轻。


Mark-Sweep 和 Mark-Compact 是 V8 中使用的另一种类型的垃圾收集器。另一个名称是 full garbage collector. 它标记所有活动节点,然后清除所有死节点并整理内存碎片。


GC Performance and Debugging Tips

虽然对于 Web 应用程序来说,高性能可能不是什么大问题,但您仍然希望不惜一切代价避免泄漏。 在 full GC 的标记阶段,应用程序实际上会暂停,直到垃圾收集完成。 这意味着堆中的对象越多,执行 GC 所需的时间就越长,用户等待的时间也就越长。


ALWAYS GIVE NAMES TO CLOSURES AND FUNCTIONS

当所有闭包和函数都有名称时,检查堆栈跟踪和堆会容易得多。

image.png

当 x(a,b) 第一次运行时,V8 创建了一个单态 IC。 当您第二次调用 x 时,V8 会擦除旧 IC 并创建一个新的多态 IC,该 IC 支持整数和字符串两种类型的操作数。 当您第三次调用 IC 时,V8 重复相同的过程并创建另一个级别为 3 的多态 IC。


但是,有一个限制。 在 IC 级别达到 5(可以使用 –max_inlining_levels 标志更改)后,该函数变得超态,不再被认为是可优化的。


直观上可以理解,单态函数运行速度最快,内存占用也更小。


DON’T ADD LARGE FILES TO MEMORY

这是显而易见的,也是众所周知的。 如果您有大文件要处理,例如一个大 CSV 文件,请逐行读取并以小块处理,而不是将整个文件加载到内存中。 在极少数情况下,单行 csv 会大于 1mb,因此您可以将其放入新空间。


DO NOT BLOCK MAIN SERVER THREAD

如果您有一些需要一些时间来处理的热门 API,例如调整图像大小的 API,请将其移至单独的线程或将其转换为后台作业。 CPU 密集型操作会阻塞主线程,迫使所有其他客户等待并继续发送请求。 未处理的请求数据会堆积在内存中,从而迫使 full GC 需要更长的时间才能完成。


DO NOT CREATE UNNECESSARY DATA

我曾经对restify有过奇怪的经历。 如果您向无效 URL 发送数十万个请求,那么应用程序内存将迅速增长到数百兆字节,直到几秒钟后完全 GC 启动,此时一切都会恢复正常。 事实证明,对于每个无效的 URL,restify 会生成一个新的错误对象,其中包含长堆栈跟踪。 这迫使新创建的对象在大对象空间而不是新空间中分配。


在开发过程中访问这些数据可能非常有帮助,但在生产中显然不需要。 因此规则很简单——除非您确实需要,否则不要生成数据。


总结

了解 V8 的垃圾收集和代码优化器的工作原理是提高应用程序性能的关键。 V8 将 JavaScript 编译为原生程序集,在某些情况下,编写良好的代码可以获得与 GCC 编译的应用程序相当的性能。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
10014 0
Juc17_ThreadLocal概述、解决SimpleDateFormat出现的异常、内存泄漏、弱引用、remove方法(一)
①. ThreadLocal简介 ①. ThreadLocal是什么 ②. api介绍 ③. 永远的helloword ④. 通过上面代码总结
10 0
Android 如何有效的解决内存泄漏的问题
前言:最近在研究Handler的知识,其中涉及到一个问题,如何避免Handler带来的内存溢出问题。在网上找了很多资料,有很多都是互相抄的,没有实际的作用。 本文的内存泄漏检测工具是:LeakCanary  github地址:https://github.com/square/leakcanary     什么是内存泄漏? 内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗。
758 0
Node.js 应用故障排查手册 —— 综合性 GC 问题和优化
本章前面两节生产案例分别侧重于单一的 CPU 高和单一的内存问题,我们也给大家详细展示了问题的定位排查过程,那么实际上还有一类相对更复杂的场景——它本质上是 V8 引擎的 GC 引发的问题。
1330 0
一个内存增长问题的分析和处理(二)——valgrind工具的用法
valgrind是linux下对C++和C程序进行内存泄露检测的工具,除了内存检测,valgrind还提供了很多其他的功能,这里主要介绍下valgrind的内存检测的功能。   首先是文件的下载,valgrind的官方网址是http://valgrind.org/,最新版本的valgrind是3.9,下载地址如下:http://valgrind.org/downloads/。
773 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13814 0
使用 Chrome Dev tools 分析应用的内存泄漏问题
使用 Chrome Dev tools 分析应用的内存泄漏问题
24 0
1727
文章
0
问答
来源圈子
更多
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载