前言
起因是因为一个项目开发到后期文件数量越来越多,本地环境运行时有时候热重载失败断开服务。网络上绝大多数的解决方案是提高上限,假使通过其他手段定位内存泄漏问题,一般方案就是找那些不被使用又不会被释放的变量,处理了这些变量,问题一般就可以解决了。
vue-cli-service serve
命令会启动一个开发服务器 (基于 webpack-dev-server ) 并附带开箱即用的模块热重载 (Hot-Module-Replacement)。
vue-cli3.0并不支持直接添加--max-old-space-size
去提高内存上限,需要安装依赖increase-memory-limit
--max-old-space-size
,最大 old space 大小,执行 MarkSweep 回收,默认 1G,单位 MB
基础知识
Node.js 进程的内存管理,都是有 V8 自动处理的,包括内存分配和释放。
在 V8 内部,会为程序中的所有变量构建一个图,来表示变量间的关联关系,当变量从根节点无法触达时,就意味着这个变量不会再被使用了,就是可以回收的了。
而这个回收是一个过程性的,从快速 GC (garbage collection) 到 最后的 Full GC,是需要一段时间的。
另外,Full GC 是有触发阈值的,所以可能会出现内存长期占用在一个高值,也可以算是一种内存泄漏;还有一种就是引用不释放,导致无法进入 GC 环节,并且一直产生新的占用,这一般会发生在 Javascript 层面。
所以,定位内存泄漏问题,一般方案就是找那些不被使用又不会被释放的变量,处理了这些变量,问题一般就可以解决了。如果是 Node.js 底层变量不释放,除了提交 issue 等待解决外,只能通过优化启动参数来解决。
流程
-
重现问题
在平时写代码的时候,可以将一些重要环节的参数细节打印在 log 中,以便于我们排查问题。
在 Node.js 的启动参数中,提供了暴露手动调用 GC 方法的参数,即--expose-gc
。我们用这个参数来启动应用后,就可以在代码中调用global.gc()
手动触发垃圾回收操作。同时,使用process.memoryUsage().heapUsed
获取进程运行时所占用的内存。如果 GC 之后,内存依然没有下降,就可以确定是内存泄露了。 -
生成内存快照
taobaofed推荐,至少要生成三次内存快照,才能更好的定位问题。这三次中又一次要在问题出现前生成,之后可以在问题持续的过程中生成两次或更多。
第一次是为了获取正常情况下的堆栈信息,而在问题出现后,堆栈信息一定会发生变化,有了第一次的信息,我们才好进行后面的比对,过滤一些无用的信息。而后两次的快照,用来比对某一对象的堆栈变化,来确定是否是有问题的对象。 -
定位问题
对于内存快照,有四个视图,Summary、Comparison、Containment、Statistics。其中Comparison可以进行多个快照比对,使用较多。而路径视图可以清楚的分辨对象被哪个变量持有,哪些不存在引用却依然存在的。 -
解决问题
一般在 Javascript 中存在引用而导致内存泄漏的情况,是比较好处理的,只需要在使用后及时的将引用释放掉即可。但如果属于底层机制的问题,如果等不了 bugfix,就只能先通过开篇讲的提高内存上限。
实际问题
taobaofed的同学很好的阐述了解决内存泄露问题的思路,那么回到我们遇到的实际问题:本地环境vue-cli3.0多次热更新后内存溢出。
查看源码,发现其也是简单调用了webpack hot-reload middleware
。那么问题的本质就是,node服务因为webpack的一次次热重载且内存泄露导致了JavaScript heap out of memory
。引起这个内存泄露的主要原因大概率不是因为webpack,而是存在于 Loaders/Plugins 或是与之相类似的。
这里我们可以借助工具:devtool,heapdump + chrome devTool 或者是 memwatch 去查看内存快照。
解决办法:找到引起问题的依赖或者是代码中会严重引起内存泄露的地方修改。若是依赖的问题,通过git issue和release log查看是否确实存在问题且已经修复再update。
心得体会
虽然起因和node服务内存泄露问题后所差距,但是问题原因和解决思路基本是一致的。相比解决问题,定位排查问题的根源更加困难。同时平时写代码也要多注意内存泄露的问题,养成良好的习惯。
网友评论