关于node.js:🤔️-什么项目构建时内存溢出了了解一下-node-内存限制

背景

在之前的一篇文章中, 咱们遇到了一个我的项目在构建时内存溢出的问题。

过后的解决方案是: 间接调大 node 的内存限度,防止达到内存下限。

明天听共事分享了一个新办法,感觉不错, 特此记录, 顺便分享给大家, 心愿对大家有所帮忙。

注释

间接上报错示意图:

提醒曾经很显著: Javascript Heap out of memory.

看到内存溢出这个关键字,咱们个别都会思考到是因为 Node.js 内存不够导致的。

但 Node 过程的内存限度会是多少呢?

在网上查阅了到如下形容:

Currently, by default V8 has a memory limit of 512mb on 32-bit systems, and 1gb on 64-bit systems. The limit can be raised by setting –max-old-space-size to a maximum of ~1gb (32-bit) and ~1.7gb (64-bit), but it is recommended that you split your single process into several workers if you are hitting memory limits.

翻译一下:

以后,默认状况下,V8在32位零碎上的内存限度为512mb,在64位零碎上的内存限度为1gb。

能够通过将--max-old-space-size设置为最大〜1gb(32位)和〜1.7gb(64位)来进步此限度,然而如果达到内存限度, 建议您将单个过程拆分为多个工作过程

如果你想晓得本人电脑的内存限度有多大, 能够间接把内存撑爆, 看报错。

运行如下代码:

// Small program to test the maximum amount of allocations in multiple blocks.
// This script searches for the largest allocation amount.

// Allocate a certain size to test if it can be done.
function alloc (size) {
  const numbers = size / 8;
  const arr = []
  arr.length = numbers; // Simulate allocation of 'size' bytes.
  for (let i = 0; i < numbers; i++) {
    arr[i] = i;
  }
  return arr;
};

// Keep allocations referenced so they aren't garbage collected.
const allocations = []; 

// Allocate successively larger sizes, doubling each time until we hit the limit.
function allocToMax () {
  console.log("Start");

  const field = 'heapUsed';
  const mu = process.memoryUsage();

  console.log(mu);

  const gbStart = mu[field] / 1024 / 1024 / 1024;

  console.log(`Start ${Math.round(gbStart * 100) / 100} GB`);

  let allocationStep = 100 * 1024;

  // Infinite loop
  while (true) {
    // Allocate memory.
    const allocation = alloc(allocationStep);
    // Allocate and keep a reference so the allocated memory isn't garbage collected.
    allocations.push(allocation);
    // Check how much memory is now allocated.
    const mu = process.memoryUsage();
    const gbNow = mu[field] / 1024 / 1024 / 1024;

    console.log(`Allocated since start ${Math.round((gbNow - gbStart) * 100) / 100} GB`);
  }

  // Infinite loop, never get here.
};

allocToMax();

不出意外, 你将喜提如下报错:

我的电脑是 Macbook Pro masOS Catalina 16GB,Node 版本是 v12.16.1,这段代码大略在 1.6 GB 左右内存时候抛出异样。

那咱们当初晓得 Node Process 的确是有一个内存限度的, 那咱们就来增大它的内存限度再试一下。

node --max-old-space-size=6000 来运行这段代码,失去如下后果:

内存达到 4.6G 的时候也溢出了。

你可能会问, node 不是有内存回收吗?这个咱们在上面会讲。

应用这个参数:node --max-old-space-size=6000, 咱们减少的内存中老生代区域的大小,比拟暴力。

就像上文中提到的: 如果达到内存限度, 建议您将单个过程拆分为多个工作过程

这个我的项目是一个 ts 我的项目,ts 文件的编译是比拟占用内存的,如果把这部分独立成一个独自的过程, 状况也会有所改善。

因为 ts-loader 外部调用了 tsc,在应用 ts-loader 时,会应用 tsconfig.js配置文件。

当我的项目中的代码变的越来越多,体积也越来越宏大时,我的项目编译工夫也随之减少。

这是因为 Typescript 的语义查看器必须在每次重建时查看所有文件

ts-loader 提供了一个 transpileOnly 选项,它默认为 false,咱们能够把它设置为 true,这样我的项目编译时就不会进行类型查看,也不会输入申明文件。

对一下 transpileOnly 别离设置 falsetrue 的我的项目构建速度比照:

  • 当 transpileOnly 为 false 时,整体构建工夫为 4.88s.
  • 当 transpileOnly 为 true 时,整体构建工夫为 2.40s.

尽管构建速度晋升了,然而有了一个弊病: 打包编译不会进行类型查看

好在官网举荐了这样一个插件, 提供了这样的能力: fork-ts-checker-webpack-plugin

官网示例的应用也非常简单:

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')

module.exports = {
  ...
  plugins: [
    new ForkTsCheckerWebpackPlugin()
  ]
}

在我这个理论的我的项目中,vue.config.js 批改如下:

configureWebpack: config => {
 // get a reference to the existing ForkTsCheckerWebpackPlugin
 const existingForkTsChecker = config.plugins.filter(
   p => p instanceof ForkTsCheckerWebpackPlugin,
 )[0];

// remove the existing ForkTsCheckerWebpackPlugin
// so that we can replace it with our modified version
 config.plugins = config.plugins.filter(
   p => !(p instanceof ForkTsCheckerWebpackPlugin),
 );

 // copy the options from the original ForkTsCheckerWebpackPlugin
 // instance and add the memoryLimit property
 const forkTsCheckerOptions = existingForkTsChecker.options;
 
 forkTsCheckerOptions.memoryLimit = 4096;
 
 config.plugins.push(new ForkTsCheckerWebpackPlugin(forkTsCheckerOptions));
}

批改之后, 构建就胜利了。

对于垃圾回收

在 Node.js 外面,V8 主动帮忙咱们进行垃圾回收, 让咱们简略看一下V8中如何解决内存。

一些定义

  • 常驻集大小:是RAM中保留的过程所占用的内存局部,其中包含:

    1. 代码自身
  • stack:蕴含原始类型和对对象的援用
  • 堆:存储援用类型,例如对象,字符串或闭包
  • 对象的浅层大小:对象自身持有的内存大小
  • 对象的保留大小:删除对象及其相干对象后开释的内存大小

垃圾收集器如何工作

垃圾回收是回收由应用程序不再应用的对象所占用的内存的过程。

通常,内存调配很便宜,而内存池用完时收集起来很低廉。

如果无奈从根节点拜访对象,则该对象是垃圾回收的候选对象,因而该对象不会被根对象或任何其余流动对象援用。

根对象能够是全局对象,DOM元素或局部变量。

堆有两个次要局部,即 New SpaceOld Space

新空间是进行新调配的中央。

在这里收集垃圾的速度很快,大小约为1-8MB

留存在新空间中的物体被称为新生代

在新空间中幸存下来的物体被晋升的旧空间-它们被称为老生代

旧空间中的调配速度很快,然而收集费用很高,因而很少执行。

node 垃圾回收

Why is garbage collection expensive?

The V8 JavaScript engine employs a stop-the-world garbage collector mechanism.

In practice, it means that the program stops execution while garbage collection is in progress.

通常,约20%的年轻一代能够存活到老一代,旧空间的收集工作将在耗尽后才开始。

为此,V8 引擎应用两种不同的收集算法

  1. Scavenge: 速度很快,可在新生代上运行,
  2. Mark-Sweep: 速度较慢,并且能够在老生代上运行。

篇幅无限,对于v8垃圾回收的更多信息,能够参考如下文章:

  1. http://jayconrod.com/posts/55…
  2. https://juejin.cn/post/684490…
  3. https://juejin.cn/post/684490…

总结

小小总结一下,上文介绍了两种形式:

  1. 间接加大内存,应用: node --max-old-space-size=4096
  2. 把一些耗内存过程独立进来, 应用了一个插件: fork-ts-checker-webpack-plugin

心愿大家留个印象, 记得这两种形式。

好了, 内容就这么多, 谢谢。

满腹经纶,如有谬误, 欢送斧正。

谢谢。

相干材料

  1. https://www.cloudbees.com/blo…
  2. http://jayconrod.com/posts/55…
  3. https://blog.risingstack.com/…

最初

如果感觉内容有帮忙, 能够关注下我的公众号,把握最新动静,一起学习!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理