目录
[1、标记 - 清除算法](#1、标记 - 清除算法)
[3、标记 - 整理算法](#3、标记 - 整理算法)
[六、Minor GC 和 Full GC 的区别](#六、Minor GC 和 Full GC 的区别)
一、什么是垃圾回收
垃圾回收****是指自动清理程序中不再使用的内存,把 " 没用的内存 " 回收再利用,避免内存泄漏、程序卡顿
- **Java、Go、Python、C#:**自带垃圾回收,自动管理内存
- **C/C++:**没有GC,需要程序员手动申请、手动释放内存,忘记释放就会导致内存泄漏
**缺点:**GC工作时会短暂暂停程序,对高实时场景有轻微影响
二、为什么需要垃圾回收
- 手动管理内存极其容易出错(人是靠不住的)
像C++这种没有GC的语言,需要手动申请内存、手动释放内存- 没有GC大程序很难写出来,GC让开发者只关注业务逻辑,不用管内存死活
像C++这种则依靠手动管理 + 智能指针- 没有GC,内存碎片会让程序卡死
手动释放会使内存 " 碎片化 ",如果想要申请一段连续的内存,会申请不成功垃圾回收的具体流程(回收机制):
- 找出谁是垃圾(不再使用的对象)
- 释放垃圾对象对应的内存
在Java中,判断一个对象是否是垃圾,就是找这个对象是否有引用指向,如果一个对象没有引用指向,此时就可以认为这个对象不再使用了,像下面的代码只有a,b,c都为null时Test对象才可被释放
javaTest a = new Test(); Test b = a; Test c = b;第一种方案为:引用计数
引用计数不是Java使用的方案,而是Python / PHP使用的方案
对每个对象增加一个空间,这个空间存储一个整数,表示指向这个对象的引用个数,围绕对象进行引用复制时,会更新这个计数,在一定时间后如果计数是0,那么这个对象就可以释放
引用计数缺点:
- 可能会消耗更多的内存空间
如果对象本身很大,那么计数器可以忽略不计,如果对象本身只有四字节,计数器占了两字节,那么就占了50%的空间- 产生循环引用,会导致误判
第二种方案为:可达性分析
在Java中,一系列对象、引用存在一定的关系,类似于一个树型结构(本质是有向图遍历),从" 根节点 "出发,所有可以通过引用找到的对象 = 存活,找不到的 = 垃圾
现在以简单的代码举例:
javaclass Test{ A a = new A(); B b = new B(); C c = new C(); } Test t = new Test(); class A{ D d = new D(); E e = new E(); } class B{ F f = new F(); } ....此代码所映射的树型结构为:
可达性分析就是从树根节点出发(实际上有多个),尝试遍历这个对象树,遍历过程中凡是经过的对象,都标记为可达,JVM知道自己一共有多少对象,除了可以到达的,剩下的就是不可达
上述 " 可达性分析 " 需要周期性进行,对象的引用是实时变化的,一轮GC需要尽可能将GC Roots遍历,尽可能标记可达
我们需要了解两个概念:
- GC Roots(GC根节点)
系统认定一定不能回收的对象- 引用链
对象之间的引用关系(如上述A指向D、E)有哪些对象可以当 GC Roots?
- 栈上的局部变量(栈有很多,其上的栈帧也有很多,每个栈帧中局部变量也有很多)
- 常量池引用指向的对象(Integer 值 -128~127 会提前创建对象)
- 所有的引用类型静态成员
可达性分析优缺点:
- 没有额外的内存空间消耗,解决了循环引用
- 会消耗更多的CPU资源,是在用时间换空间
三、Java堆内存结构与分代模型

1、新生代(Eden区+Survivor区)
YGC(Minor GC)发生区,复制算法
- Eden 伊甸区
新建对象优先分配在 Eden,Eden满触发 Minor GC- Survivor 幸存区,S0,S1
永远一块在用,一块空闲,每经过一次GC,年龄+1,默认年龄15升入老年代
2、老年代
Old(Major GC),标记整理/标记清除
- 长期存活的对象:年龄达到阈值
- Eden大对象直接进入老年代,避免复制开销
老年代空间不足触发Full GC
四、常见的垃圾回收算法
1、标记 - 清除算法
把标记出来的垃圾,直接释放掉
现在对这些模块进行释放,这些被释放的内存在地址上是**" 离散的 "** (不连续),这就会导致内存碎片化 问题,在申请内存时都是申请连续的内存 ,当我们想要申请一个大内存时,会申请失败,但总空闲内存是足够的
在进程中,哪些对象是垃圾也是随机的,很可能在频繁的释放中产生大量的内存碎片
2、复制算法
将内存区域划分为两块,每次只对其中的一块进行操作,可以有效解决内存碎片问题
现在将1、3标记为垃圾,然后对2、3、4进行复制操作,将它们复制到右边的区域,最后将左边区域全部GC
这样做有两个缺点:
- 空间利用率很低,每次只使用50%的区域
- 如果存活的对象很多,那么复制的开销就很大
3、标记 - 整理算法
标记为垃圾的内存释放后,对现有的区域进行搬运
现在将2、5、7区域释放,对剩余区域进行搬运。
对于空间利用率得到了改善,但开销同样可能很大
五、分代回收的核心思想与工作流程
核心思想:依据对象存活生命周期长短分代,不同代使用不同的垃圾回收算法,优化回 收效率
堆划分为新生代 (Eden区+From Survivor+To Survivor )和老年代
工作流程:
- 新new的对象,放在伊甸区,在经过第一轮GC后,绝大多数对象会被淘汰掉(经验规律:大部分对象生命周期很短("朝生夕死"))
- 没有淘汰掉的对象通过复制算法,进入到幸存区(幸存区有两个部分,每次只使用其中的一块),下一轮GC会对幸存区对象进行扫描,还会淘汰掉一部分
- 没有被淘汰的对象,进入到另一个幸存区,由于每轮GC会淘汰掉大部分对象,所以进行复制算法的对象就不会很多,这就解决了复制算法开销大的问题,另一方面幸存区只占到新生代的20%,浪费的空间就比较小了
- 随着GC的周期性进行,对象每进行一次拷贝,其年龄就 +1
- 经过一定时间后,对象的年龄达到一定阈值,会拷贝进入老年代
- 对象进入老年代后,进行GC的频率就降低了,虽然整理一次的开销较大,但频率比较低
- 如果某个对象所占内存较大,就会直接进入老年代
六、Minor GC 和 Full GC 的区别
它们都是Java垃圾回收的核心机制
- Minor GC是针对新生代GC,触发条件是Eden区满,使用复制算法,特点是频率高,速度快,时间短
- Full GC是针对新生代+老年代+元空间,触发条件是老年代空间不足,特点是频率低,速度慢,时间长
七、垃圾回收器的典型实现
上述的分代回收是一个简化版本,也是一种思想方法
在垃圾回收时候需要考虑到效率问题,需要考虑对业务代码是否有影响
JVM垃圾回收器:
- CMS:并发收集,多停顿,尽可能进行多线程标记,尽可能不影响业务逻辑
缺点:内存碎片化,占用CPU- G1:分代+分区回收,能够处理内存空间特别大的情况,将整个内存区域划分出更多的空间,一次GC只回收其中的一部分
- ZGC:新增分代设计,追求亚毫秒级STW,支持TB级超大内存






