Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

学习内存,缓存和垃圾回收相关知识 #36

Open
creeperyang opened this issue Aug 23, 2017 · 1 comment
Open

学习内存,缓存和垃圾回收相关知识 #36

creeperyang opened this issue Aug 23, 2017 · 1 comment

Comments

@creeperyang
Copy link
Owner

1. 垃圾回收

此段为 朴灵 《深入浅出 Node.js》 阅读笔记。

在 V8 中,所有 JavaScript 的对象都是通过 堆来分配 的。

当我们在代码中声明变量并赋值时,所使用对象的内存就分配在堆中。如果已申请的堆空闲内存不够分配新的对象,将继续申请堆内存,直到堆大小超过V8的限制。

那为什么 V8 要限制堆大小?

  • 表层原因:V8 最初为浏览器而设计,无大内存场景。
  • 深层原因:V8 垃圾回收机制限制。以 1.5GB 的垃圾回收为例,V8 做一次小的垃圾回收需要 50 毫秒以上,而一次非增量式垃圾回收需要 1 秒以上。垃圾回收引起 JS 线程暂停执行,这么长时间是不可接受的。

V8 的垃圾回收机制

V8 的垃圾回收策略主要基于 分代式垃圾回收 机制。因为实际应用中,对象的生存周期长短不一,不同的算法只针对特定情况有最好效果,所以现代垃圾回收算法中,按对象存活时间将内存的垃圾回收进行不同的分代,然后分别运用不同算法。

V8 的内存分代

V8 主要将内存分为 新生代老生代 两代。新生代中的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象。

新生代:Scavenge 算法

新生代的对象主要通过 Scavenge 算法进行垃圾回收。而 Scavenge 的具体实现中,采用 Cheney 算法—— 一种采用复制方式实现的垃圾回收算法:

  1. 将堆内存一分为二,每个部分空间称为 semispace;
  2. 两个 semispace 一个处于使用中(称为 From 空间),一个处于空闲状态(称为 To 空间);
  3. 当我们分配对象时,在 From 空间进行分配;
  4. 当开始进行垃圾回收时,会检查 From 空间的存活对象,把它们复制到 To 空间,而非存活对象占用的空间被释放;
  5. 完成复制后,From 空间和 To 空间角色对换。

Scavenge 的缺点是只能使用堆内存的一半,但由于只复制存活对象,并且由于生命周期短的场景中存活对象只占少部分,所以它在时间效率上不错。

当一个对象经过多次复制依然存活时,它会被认为是生命周期较长的对象,会被移动到老生代中,采用新的算法进行管理。对象从新生代移动到老生代称为晋升。

不同于单纯的 Scavenge 过程,在分代式垃圾回收的前提下,From 空间的存活对象复制到 To 空间前需要进行检查:即是否可以晋升。

晋升的两个条件:

  • 对象是否经历过(一次) Scavenge 回收;
  • To 空间的内存占用超过一定比例,比如 25%。设置比例是因为此次 Scavenge 回收完成后, To 空间将变成 From 空间,占用比例过高将影响后续内存的分配。

老生代:Mark-Sweep & Mark-Compact

对于老生代,由于存活对象占比高,采用 Scavenge 会有两个问题:

  • 存活对象多,复制效率会很低;
  • 依然存在浪费一半空间的问题。

所以老生代采用 Mark-Sweep 和 Mark-Compact 相结合的方式进行垃圾回收。

Mark-Sweep,即标记清除 ,分为标记和清除两个阶段。

  • 标记阶段,遍历对中所有对象,并标记活着的对象。
  • 清除阶段,只清除没有被标记的对象。

相比 Scavenge ,Mark-Sweep 不存在浪费空间的行为,只清理死亡对象。

当 Mark-Sweep 最大的问题是在 进行一次标记清除回收后,内存空间会存在不连续的状态 。这种内存碎片会对后续的内存分配造成问题,因为很可能出现需要分配一个大对象的情况,这时所有的碎片空间都无法完成此次分配,就会提前触发垃圾回收,而这次垃圾回收是不必要的。

Mark-Compact 可以解决内存碎片的问题,Mark-Compact 是 标记整理 ,在 Mark-Sweep 基础上演变而来。它们的差别在于在标记后,在整理的过程中,将活着的对象向一端移动,移动完成后,直接清理掉边界外的内存。

Incremental Marking 增量标记

为避免出现 JavaScript 应用逻辑与垃圾回收器看到的不一致的情况,以上 3 种垃圾回收算法都需要将应用逻辑暂停,执行回收后再运行——即全停顿(stop-the-world)。

为了降低全堆垃圾回收带来的停顿时间,V8 从标记阶段入手,将全量标记改为增量标记,垃圾回收与应用逻辑交替执行,直到标记阶段完成。

@creeperyang
Copy link
Owner Author

Java Garbage Collection Basics

可配合Java垃圾收集对照阅读。可以看到,垃圾收集原理都是一致的。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant