东哥学Node的故事——内存管理
前言
东哥是一个平凡的前端攻城狮,北邮网研院研二在读,刚接触Node不久,心里充满了对Node的好奇和崇拜,只听噗通一声,掉入了Node的坑。。。
于是东哥开始疯狂地看Node相关的书籍,这不,就学到了Node.js内存管理这一章。
他读到:“对于那些短时间执行的场景,比如网页应用、命令行工具,内存的管理似乎没有太大的必要。因为运行时间短,随着进程的退出,内存得到释放,几乎没有内存泄露,即使存在内存使用过多的情况,也只会影响到终端用户。所以,我们在使用JavaScript进行前端开发的过程中,很少会考虑内存管理的问题”。
东哥觉得很有道理,产生了共鸣:“是啊,干了这么久前端,写了这么多网页,还真没考虑过内存问题!”
顺便提一句,东哥还是一个算法狂人,曾经使用Java这门走遍天下的语言刷遍了各大平台(Leetcode、剑指offer、牛客网。。。)的算法题,可谓十分强势!
东哥很聪明,心想:既然Node.js是一个针对服务器端开发的平台,也应该和Java一样存在一些诸如内存泄露、内存分配优化等问题吧。
他读到:“随着Node.js在服务器端的广泛应用,其他语言在内存管理上存在的问题在JavaScript中也暴露了出来。”
心想:“有道理,让俺一睹其究竟!”
V8垃圾回收机制与内存限制
Node与V8
2009年,Node的创始人Ryan Dahl选择了V8来作为Node的JavaScript脚本引擎,在第三次浏览器大战中,Google的Chrome浏览器凭借V8的优异性能成为焦点。
V8内存限制
在一般的后端开发语言中,在基本的内存使用上没有什么限制,然而在Node中通过JavaScript使用内存是就会发现只能使用部分内存(64位系统下约为1.4GB,32位系统下约为0.7GB)。
最近,东哥刚入手了一台32GB内存的服务器用于大数据分析处理,有一天,他试图将一个2GB的文件读入内存中进行字符串分析处理,这岂不是小菜一碟?可是。。。东哥失败了。。。
造成这个问题的主要原因在于Node是基于V8构建的,所以在Node中使用的JavaScript对象基本上都是通过V8自己的方式进行管理分配的。V8的这套内存管理机制对于浏览器端使用起来可谓绰绰有余,但在服务器端却大大限制了开发者随心所欲地使用大内存的想法。
V8对象分配
在V8中,所有的JavaScript对象都是通过堆来进行分配的。V8堆示意图如下:

可以使用process.memoryUsage()来查看内存使用量:

其中,rss是resident set size的缩写,即进程的常驻内存部分。进程的内存总共有几部分,一部分是rss,其余部分在交换区(swap)或者文件系统(filesystem)中;除了rss外,heapTotal和heapUsed对应V8堆内存的信息,heapTotal是堆中总共申请的内存空间,heapUsed是目前堆中使用的内存空间。
除此以外,还可以使用os模块的totalmem()和freemem()两个方法查看操作系统的内存使用情况,它们分别返回的是系统的总内存和闲置内存,以字节为单位:

可见,我这台屌丝机系统总内存为4GB,当前闲置内存大致为2.8GB。
东哥在熟悉了查看内存信息的方法后一直很纳闷,V8为什么要限制堆的大小呢,这样做是不是会让Node内存使用性能变得很低下?
表层原因是因为V8最初是为浏览器而设计的,不太可能有用到大量内存的情况。而深层原因是V8的垃圾回收机制的限制。
当然,我们也可以自行配置内存使用空间,因为V8也给开发者提供了选项让我们使用更多的内存。示例如下:
node --max-old-space-size=1700 test.js //单位为MB
node --max-new-space-size=1700 test.js //单位为KB
V8垃圾回收机制
V8的垃圾回收策略主要是基于分代式垃圾回收机制。在V8中,主要将内存分为新生代和老生代。新生代中的对象为存活时间较短的对象,老生代中的对象为存活时间较长的或常驻内存的对象。

涉及到的垃圾回收算法算法主要有三种:

关于算法的细节,由于时间有限,就不在这里详细描述了,大家可以对比着看看各种算法的特点。
如何查看垃圾回收日志?
查看垃圾回收日志的方式主要是在启动时添加--trace_gc参数。将会在gc.log文件中得到所有的垃圾回收信息:

gc.log文件大概长这个样:

通过查看gc.log文件,我们可以找出回收哪些阶段比较耗时,触发的原因是什么。
另外,通过在Node启动时使用--prof参数,可以得到V8执行时的性能分析数据,其中包含了垃圾回收执行时占用的时间。我在本地写了一个test.js文件:
for(var i=0;i<1000000;i++){
var a = {};
}
执行如下命令:

会在目录下得到一个v8.log日志文件,长这样:

显然,该文件不具备可读性。。。所幸,V8提供了linux-tick-processor工具用于统计日志信息。我们执行:

就能得到统计结果,大致如下:

统计内容较多。其中,垃圾回收部分如下:

由于不断分配对象,垃圾回收所占的时间为5.4%.按此比例,时间循环执行1000毫秒的过程中要给出54毫秒用于垃圾回收。
高效使用内存
作用域(链)
在JavaScript中能形成作用域的有函数调用、with语句以及全局作用域。以如下代码为例:
var foo = function(){
var local = {};
};
foo()函数在每次被调用时会创建对应的作用域,函数执行结束后,该作用域将会销毁。同时作用域中申明的局部变量随着作用域的销毁而销毁,局部变量local失效,其引用的对象将会在下次垃圾回收时被释放。
而作用域链是指JavaScript执行过程中变量的查找会沿着一层一层的作用域形成的链进行,一直到全局作用域。
所以,主动释放变量可以合理地利用作用域(链)原理,让我们高效地使用内存。
闭包
闭包大家再熟悉不过了,在JavaScript中,实现外部作用域访问内部作用域中的变量的方法叫做闭包。而闭包的存在,会使得局部变量常驻内存,不能被及时释放回收。
所以,闭包要慎用。即使使用,也要在适当的时候主动释放局部变量。
内存泄露
内存泄露的原因主要有如下几个:
1、缓存
2、队列消费不及时
3、作用域未及时释放
所以,预防措施主要有如下几个:
1、慎将内存当做缓存使用
2、关注队列状态
3、及时释放作用域中的对象和变量
内存泄露排查
推荐几个排查工具,可通过npm安装使用:
1、v8-profiler
2、node-heapdump
3、node-mtrace
4、dtrace
5、node-memwatch
东哥学Node的故事——内存管理的更多相关文章
- 24小时学通Linux内核之内存管理方式
昨天分析的进程的代码让自己还在头昏目眩,脑子中这几天都是关于Linux内核的,对于自己出现的一些问题我会继续改正,希望和大家好好分享,共同进步.今天将会讲诉Linux如何追踪和管理用户空间进程的可用内 ...
- 十天学Linux内核之第三天---内存管理方式
原文:十天学Linux内核之第三天---内存管理方式 昨天分析的进程的代码让自己还在头昏目眩,脑子中这几天都是关于Linux内核的,对于自己出现的一些问题我会继续改正,希望和大家好好分享,共同进步.今 ...
- node 内存管理相关
为什么在node中要担心node内存管理 使用JavaScript进行前端开发时几乎完全不需要关心内存管理问题,对于前端编程来说,V8限制的内存几乎不会出现用完的情况,v8在node中有着内存的限制( ...
- Linux内存描述之内存节点node–Linux内存管理(二)
日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 #1 ...
- 零基础学python-3.5 内存管理
* 变量无需事先声明 * 变量无需指定类型 * 程序猿不用关系内存管理 * 变量名会被回收 * del能够直接释放资源 1.python使用的是引用调用,而不是值调用,他使用的回收算法是引用计数算法, ...
- 《从零开始学Swift》学习笔记(Day 61)——Core Foundation框架之内存管理
原创文章,欢迎转载.转载请注明:关东升的博客 在Swift原生数据类型.Foundation框架数据类型和Core Foundation框架数据类型之间转换过程中,虽然是大部分是可以零开销桥接,零开销 ...
- 《从零開始学Swift》学习笔记(Day 61)——Core Foundation框架之内存管理
原创文章,欢迎转载. 转载请注明:关东升的博客 在Swift原生数据类型.Foundation框架数据类型和Core Foundation框架数据类型之间转换过程中,尽管是大部分是能够零开销桥接,零开 ...
- 东哥读书小记 之 《MacTalk人生元编程》
一直以来的自我感觉:自己是个记性偏弱的人.反正从小读书就喜欢做笔记(可自己的字写得巨丑无比,尼玛不科学呀),抄书这事儿真的就常发生俺的身上. 因为那时经常要背诵课文之类,反正为了怕自己忘记, ...
- 跟着鸟哥学Linux系列笔记3-第11章BASH学习
跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 跟着鸟哥学Linux系列笔记1 跟着鸟哥学Linux系列笔记2-第10章VIM学习 认识与学习bash 1. ...
随机推荐
- day18 装饰器(下)+迭代器+生成器
目录 一.有参装饰器 1 前提 2 如何使用有参装饰器 3 有参装饰器模板 4 修正装饰器 二.迭代器 1 什么是迭代器 2 为什么要有迭代器 3 如何用迭代器 3.1 可迭代对象 3.2 可迭代对象 ...
- SSTI(模板注入)
SSTI 一. 什么是SSTI 模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档. ...
- CRM开发系列
CRM,客户关系管理系统(Customer Relationship Management).企业用CRM技术来管理与客户之间的关系,以求提升企业成功的管理方式,其目的是协助企业管理销售循环:新客户的 ...
- 定时器三----js定时器
方法一: var t; //初始化定时器 $(function(){ init_fun_timer1(); }); ...
- Web Scraping using Python Scrapy_BS4 - using BeautifulSoup and Python
Use BeautifulSoup and Python to scrap a website Lib: urllib Parsing HTML Data Web scraping script fr ...
- Intelij DataGrip 的安装和使用
链接: Intelij DataGrip 安装教程以及汉化教程 Intelij DataGrip 使用教程 以上两个教程已使用过,没有问题 如有侵权请联系删除
- DJANGO-天天生鲜项目从0到1-009-购物车-Ajax实现添加至购物车功能
本项目基于B站UP主‘神奇的老黄’的教学视频‘天天生鲜Django项目’,视频讲的非常好,推荐新手观看学习 https://www.bilibili.com/video/BV1vt41147K8?p= ...
- Android应用内部实现多语言,一键切换语言,国际化适配
1.首先提供多语言对应的string值 如en对应英语, fr对应法语 两个文件中包含同样的key, 对应不同的语言的value 2.java代码相应用户切换语言动作 private static v ...
- css中的名词
用到的单词 current 当前 hover 悬停 selected 挑选 disabled 禁用 focus 得到焦点 blur 失去焦点 checked 勾选 success 成功 error 出 ...
- JavaScript运算符与流程控制
JavaScript运算符与流程控制 运算符 赋值运算符 使用=进行变量或常量的赋值. <script> let username = "YunYa"; < ...