个人博客:haichenyi.com。感谢关注
一. 目录
- 一–目录
- 二–Java垃圾回收机制
- 三–JavaScript垃圾回收机制
说到垃圾回收机制,就想到之前Android的java的垃圾回收机制,什么堆栈方法区,新生代,老年代,fullGC,复制算法,标记清除算法,可达性分析等等全是重点。想搞懂java的垃圾回收机制,这些全都要知道。
二. Java垃圾回收机制
JavaScript与Java的垃圾回收机制思路都是一样的。具体的算法实现不同。先简单的回顾一下(大致可以这么理解,不是绝对的):
- 堆栈方法区:new的对象放到堆中,局部变量放到栈中,静态变量,常量放在方法区中
- 新生代:新创建的小对象放到新生代中
- 老年代:新创建的大对象放到老年代中
- 复制算法:就是把新生代的对象复制到老年代中。具体的算法逻辑不清楚
- 标记清除算法:标记哪些对象能被清除,哪些不能被清除
- 可达性分析:哪些对象可达,哪些对象不可达
- fullGC:直接触发全局的垃圾回收
好,上面这些概念都说完了。举个例子:一个士兵的一生,从入伍到退伍。这里的士兵可以看作一个对象,入伍就是被new出来了,退伍就是被gc回收了。
-
一个士兵入伍了(对象被new出来了)。毫无背景,只能当小兵(小对象放进新生代的Eden),有很大的背景,当官了(大对象,放进老年代)
-
不断有人入伍(不断的有对象被创建)
-
国家战乱来了,士兵参战了。每次大战过后,领导下来视察(Eden区装满了,触发Minor GC)
-
领导发现,有一部分人已经牺牲了(没有引用了,找不到了,不可达对象),剩下的没有牺牲(有引用,可以找到,可达对象),把没有牺牲的人放到有经验的队伍中去(把可到对象复制到survior区),清空新人营继续招人
-
战争还未结束,继续招人,打仗,视察安慰士兵。如此反复。每个国家士兵的晋升肯定有一定规则呀。当士兵满足条件可以晋升军衔。比方说,默认是15次,打仗打15次就晋升军衔(Minor GC触发15次)。或者有经验的队伍人太多了(Survivor区域装满了),晋升军衔(把对象复制到老年代中去)
-
当军官都满员了(老年代区域也装满了),士兵无法晋升了,就要搞事情了,需要清理那些不干事的军官(触发老年代的垃圾回收机制Major GC)。也是一样的标记清除算法与可达性分析
-
正常情况下,军官营清理完一部分人(老年代清理回收对象),就有位置收下新人了,新的新人晋升。但是,要是军官营的人就是不让位置怎么呢?(都是不可回收的对象,都可达),这个时候,就需要大清洗了,新兵营,军管区都需要清理,给人空出位置来(触发Full GC)
-
要是大清理之后,还是无法清理出位置来,怎么办呢?能怎么办?领导人下台,重新组建政府,一切从新开始(OutOfMemoryError程序OOM了)
-
以上就是一个士兵的一生(一个对象从new到消亡的整体过程)。
可达性分析
- 核心作用
目的:判断堆内存中的对象是否“存活”(是否仍被程序使用)。
理论基础:通过对象之间的引用链,从“根对象”(GC Roots)出发,遍历所有可达对象,标记存活对象,不可达对象视为垃圾。 - 流程
a. GC Roots 类型:
虚拟机栈(栈帧中的局部变量)引用的对象。
方法区中静态变量(static)引用的对象。
方法区中常量(final)引用的对象。
本地方法栈(JNI)引用的 Native 方法对象。
所有被同步锁(synchronized)持有的对象。
Java 虚拟机内部的特殊对象(如系统类加载器)。
b.遍历引用链:
从 GC Roots 出发,递归遍历所有对象之间的引用关系。
存活对象:所有被 GC Roots 直接或间接引用的对象。
垃圾对象:无法通过任何引用链连接到 GC Roots 的对象。
不可达即回收:即使对象之间存在循环引用,只要它们整体不可达,也会被回收。
标记清除算法(Mark-Sweep)
-
核心作用
目的:基于可达性分析的结果,释放不可达对象占用的内存。
角色:一种具体的垃圾回收算法实现。 -
流程
标记阶段:
通过可达性分析,标记所有存活对象。
实现方式:在对象头中记录标记位(如 0 表示未标记,1 表示已标记)。
清除阶段:
遍历堆内存,释放未被标记的对象(垃圾)占用的内存。
内存碎片问题:清除后的内存空间不连续,可能导致后续分配大对象失败。(配合标记-整理算法,整理内存碎片化的问题)
两者的关系
可达性分析是垃圾回收的“判断逻辑”,用于识别哪些对象需要回收。
标记清除算法是垃圾回收的“执行逻辑”,基于可达性分析的结果释放内存。
流程图解
[对象创建] → Eden区│├─ Eden区满 → Minor GC → 存活对象复制到 Survivor区 → 年龄增加│ ││ └─ 不可达对象被回收│├─ Survivor区满或对象年龄达标 → 晋升到老年代│└─ 老年代满 → Major GC / Full GC → 标记-清除/整理 → 回收不可达对象
三. JavaScript垃圾回收机制
为什么要好费力气去说Java的垃圾回收机制呢?因为我是Android出身,当初学的Java语言,对Java的垃圾回收机制比较熟悉。并且,JavaScript V8引擎的垃圾回收机制与Java的垃圾回收机制类似。画个图就知道了
我就不像说Java的垃圾回收机制那样,说的那么细了。先直接说结论
[对象创建] → From-Space(新生代)│├─ From-Space满 → Scavenge GC → 存活对象复制到 To-Space → 晋升次数增加│ ││ └─ 不可达对象被回收│├─ 对象晋升次数达标或 To-Space 不足 → 晋升到老生代│└─ 老生代满 → 增量标记 → 标记-清除/整理 → 回收不可达对象
- 创建对象,更Java一样小对象到新生代,大对象到老年代
- 新生代分为2块:From和To。当From-Space区域触发新生代的垃圾回收机制Scavenge GC
- 还是可达性分析和标记清除算法。把可达的对象复制到To-Space(并且次数+1),不可达的对象清除。清空From区,此时所有的对象都在To区了
- 两者交换位置,也就是From和To交换位置。也就是之前的From是现在的To,现在的To变成了From
- 新创建的对象还是放进From区。目的就是From区触发GC,把对象复制到To区。简单的理解就是从From到To,再从To到From。
- 当To区放不下,或者次数达到2(阈值),就把对象赋值到老年代
- 当老年代也满了,达到阈值(默认约为堆内存的 75%),触发老年代的垃圾回收。增量回收或全堆回收:V8 采用增量标记(Incremental Marking)减少主线程阻塞。
以上就是JavaScript的垃圾回收机制
PS:整篇文章,精炼一下,如下
//Java垃圾回收机制
[对象创建] → Eden区│├─ Eden区满 → Minor GC → 存活对象复制到 Survivor区 → 年龄增加│ ││ └─ 不可达对象被回收│├─ Survivor区满或对象年龄达标 → 晋升到老年代│└─ 老年代满 → Major GC / Full GC → 标记-清除/整理 → 回收不可达对象
//JavaScript垃圾回收机制
[对象创建] → From-Space(新生代)│├─ From-Space满 → Scavenge GC → 存活对象复制到 To-Space → 晋升次数增加│ ││ └─ 不可达对象被回收│├─ 对象晋升次数达标或 To-Space 不足 → 晋升到老生代│└─ 老生代满 → 增量标记 → 标记-清除/整理 → 回收不可达对象
随着系统的升级,垃圾回收机制一直都在不断的变化。不能保证,这个垃圾回收机制就是最新的。