问题由来

因为业务的需求,某 Node.js 性能平台用户需要调节新生代大小,Node.js 的启动参数里面的max_semi_space_size可以设置新生代堆空间的大小。

node --v8-options | grep max_semi -A 3 -B 2--min_semi_space_size (min size of a semi-space (in MBytes), the new space consists of twosemi-spaces)type: int  default: 0--max_semi_space_size (max size of a semi-space (in MBytes), the new space consists of twosemi-spaces)type: int  default: 0--semi_space_growth_factor (factor by which to grow the new space)type: int  default: 2

相关文档里该值是一个以MB为单位的整数,并没有其他特别的约束。

然而,当用户设置 max_semi_space_size 为200时,Node.js 性能平台 GC Trace分析 结果显示 inactive new_space semispace:256MB,说明新生代中未使用的那部分是256MB(有关GC的一些知识可以参阅文档最后的列表),如下图所示。

我们也线下验证了一下:

  • 当设置 max_smi_space_size 为100,110时,inactive new_space semispace:128MB
  • 当设置 max_smi_space_size 为50,60时,inactive new_space semispace:64MB

那么问题来了,是 Node.js 性能平台的bug?还是v8引擎本身的设计就是如此?

问题定位

其实,从最终64/128/256,这些数值就能推测到,设计就是如此。在计算机的世界里,2的整数次幂会带来很多方便。

还是老方法,既然文档里没有明确说明,而 Node.js 性能平台运行时在该部分功能跟社区运行时是完全一致的,那么只能去代码里找原因了。
一番操作之后,在 deps/v8/src/heap/heap.cc 中找到函数:

bool Heap::ConfigureHeap(size_t max_semi_space_size, size_t max_old_space_size, size_t code_range_size))。

这里摘抄一点max_semi_space相关代码,相关注释直接写到代码里面了,感兴趣的同学建议去看看完整代码。

bool Heap::ConfigureHeap(size_t max_semi_space_size, size_t max_old_space_size,size_t code_range_size) {if (HasBeenSetUp()) return false;// Overwrite default configuration.// 未设置 max_semi_space_size 时,默认值是 0 if (max_semi_space_size != 0) {max_semi_space_size_ = max_semi_space_size * MB;}...// If max space size flags are specified overwrite the configuration.// 命令行 --max_semi_space_size 设置的新生代大小是通过 FLAG_max_semi_space_size 传递到vif (FLAG_max_semi_space_size > 0) {max_semi_space_size_ = static_cast<size_t>(FLAG_max_semi_space_size) * MB;}.../* ROUND_UP 的定义:// Round up n to be a multiple of sz, where sz is a power of 2.#define ROUND_UP(n, sz) (((n) + ((sz) - 1)) & ~((sz) - 1))*/// 操作系统相关的内存页大小,我的ubuntu16.04上该值是 512KBif (Page::kPageSize > MB) {max_semi_space_size_ = ROUND_UP(max_semi_space_size_, Page::kPageSize);...}// 该参数默认是falseif (FLAG_stress_compaction) {// This will cause more frequent GCs when stressing.max_semi_space_size_ = MB;}// The new space size must be a power of two to support single-bit testing// for containment.// 关键点在这里// 为什么 50 变成了 64, 100/120 变成了128, 200 变成了 256// 下面的函数 RoundUpToPowerOfTwo32 就是这个变化的原因。/*
uint32_t RoundUpToPowerOfTwo32(uint32_t value) {DCHECK_LE(value, uint32_t{1} << 31);if (value) --value;
// Use computation based on leading zeros if we have compiler support for that.
#if V8_HAS_BUILTIN_CLZ || V8_CC_MSVCreturn 1u << (32 - CountLeadingZeros32(value));
#elsevalue |= value >> 1;value |= value >> 2;value |= value >> 4;value |= value >> 8;value |= value >> 16;return value + 1;
#endif
}*/ max_semi_space_size_ = base::bits::RoundUpToPowerOfTwo32(static_cast<uint32_t>(max_semi_space_size_));// 这里是 min_semi_space_size 的设置,这里不讨论if (FLAG_min_semi_space_size > 0) {...}// 新生代初始大小是 min_semispace_size,如果需要,那么增大到 max_semi_space_sizeinitial_semispace_size_ = Min(initial_semispace_size_, max_semi_space_size_);if (FLAG_semi_space_growth_factor < 2) {FLAG_semi_space_growth_factor = 2;}...configured_ = true;return true;
}

问题结论

max_semi_space_size 设置看起来是个任意整数,但是实际使用中 v8 会把该值转换成一个不小于该值的2的整数次幂的值。也就是说:

  • max_semi_space_size 设置为 33, 34, ..., 64,最终结果都是 64MB。
  • max_semi_space_size 设置为 65, 66, ..., 128,最终结果都是 128MB。
  • 依次类推

heap.cc 里面的注释是

  // The new space size must be a power of two to support single-bit testing// for containment.

相关知识

  • 解读 V8 GC Log(一): Node.js 应用背景与 GC 基础知识
  • 解读 V8 GC Log(二): 堆内外内存的划分与 GC 算法

max_semi_space_size 设置值与实际值不一致的原因分析相关推荐

  1. 关于Vivado仿真时,时钟信号初值设置方法导致仿真结果不一致的问题分析

    本文以余3码加法计数器的设计为例,说明仿真激励过程中遇到的问题,解决方法及分析. 加法器电路的实现代码如下: module m10counterGU(clk,state);input clk;outp ...

  2. ADPLUS使用配置文件设置断点时无法创建DUMP文件原因分析

    ADPLUS简介 ADPLUS.vbs是Debugging Tool for Windows里带的一个VBS脚本.我们可以用它很方便地生成进程的内存转储文件.从Debugging Tool for W ...

  3. mysql 查询空字符串 设置默认值_MySQL默认值选型是空,还是 NULL-爱可生

    如果对一个字段没有过多要求,是使用""还是使用 NULL,一直是个让人困惑的问题.即使有前人留下的开发规范,但是能说清原因的也没有几个.NULL 是""吗?在辨 ...

  4. Redis中字符串string数据类型(保存(设置键值、过期时间、设置多个键值、追加值)、获取(获取单一键值、获取多个键值))

    字符串类型是 Redis 中最为基础的数据存储类型,它在 Redis 中是二进制安全的,这便意味着该类型可以接受任何格式的数据,如JPEG图像数据或Json对象描述信息等.在Redis中字符串类型的V ...

  5. python 设置 初始值_Python初始值表示为无穷大

    之前只知道设置变量的初始值为0.今天在写网络路径分析的时候,为了找到离任意坐标距离最近的节点,初始设置最短距离为无穷大,然后不断的去替换,直到找到最近的节点. 刚开始设置是min_dis = 9999 ...

  6. rails设置表单默认值amp;amp;隐藏表单

    需求: 用户没有登录时游客评论,需要游客输入用户名和评论内容:当用户已登录时,读取session获取用户名,隐藏输入用户名的表达,用户直接发表评论. 用户评论代码: <h2>Add a c ...

  7. 警告:push.default未设置;它的隐含值在Git 2.0中发生了变化

    本文翻译自:Warning: push.default is unset; its implicit value is changing in Git 2.0 I've been using Git ...

  8. 怎么设置matlab滑块的值,matlab - 如何根据另一个滑块更改滑块的最大值 - SO中文参考 - www.soinside.com...

    如果要通过slider1更改slider2的最大值,则必须在slider1的回调函数中设置slider2 'Max'值.我给你做了一个小例子: % --- Executes on slider1 mo ...

  9. htmlparser设置表单属性值

    想用htmlparser实现自动填写表单并提交,但暂时实现不了提交,计划通过httpclient实现.htmlparser可实现设置表单属性值,但貌似意义不大,其主要价值在解析html网页.设置表单属 ...

最新文章

  1. 《JS权威指南学习总结--第十一章子集和扩展》
  2. 解决报错:error: function declaration isn’t a prototype [-Werror=strict-prototypes]
  3. Hibernate之lazy延迟加载
  4. MYSQL中的主表和父表_主表,从表,关联表,父表,子表
  5. linux下安装node.js
  6. C# 9 新特性 —— 增强的模式匹配
  7. python getchar,Linux C编程学习:getchar()和getch()
  8. adams如何保存_教你如何快速把模型 从solidworks转到adams里
  9. 【论文写作】毕业论文写作必备技巧:修改文章的诀窍
  10. Local time zone must be set--see zic manual page 2017
  11. PaaS、IaaS 、SaaS、Bass、Fass、无服务的理解与区别
  12. 正弦稳态电路的阻抗和功率
  13. UG NX二次开发(C#)-装配-添加组件AddComponent
  14. 影评系统的机遇和挑战
  15. 什么是Anti-DDoS流量清洗?
  16. webpack中文官网
  17. 使用adb 命令时提示“adb”既不是内部或外部命令,也不是可运行的程序
  18. 这些设计细节虽小,但俘获了用户的心
  19. CSS-基础选择器、字体文本属性、引入方式
  20. Centos7 mysql5.7.28 Unit mysqld.service entered failed state.

热门文章

  1. Python3中闭包介绍
  2. Shell脚本基础介绍
  3. kde重启_在Linux系统中出现KDE Plasma面板崩溃问题的解决方法
  4. Java项目:宠物商城系统(java+Springboot+Maven+mybatis+Vue+mysql)
  5. Ant Design of Vue —— setFieldsValue方法 动态操作Switch组件
  6. CSS之定位(粘滞定位)
  7. 振动力学基础与matlab应用_【日文好书推荐】振动与噪声控制技术for机械设计者...
  8. 关于iOS10的适配
  9. iOS 使用fastlane自动化打包步骤
  10. iOS自动签名打包(xcodebuild)----常用