目录
一、垃圾回收定义
二、垃圾回收处理内存区域
三、垃圾标记
1、引用计数
(1)、定义
(2)、缺陷
2、可达性分析
(1)、定义
(2)、缺点
四、垃圾回收过程
1、标记清除
(1)、定义
(2)、缺点
2、复制算法
(1)、定义
(2)、缺点
3、标记整理
(1)、定义
(2)、缺点
4、分代算法
(1)、定义
(2)、回收策略
五、垃圾回收器
一、垃圾回收定义
垃圾回收机制(GC)能够帮助程序员自动释放内存。Java等编程语言引入了GC来解决内存泄漏问题,通过JVM自动判断,能够有效的减少内存泄漏的出现概率。
二、垃圾回收处理内存区域
JVM中的内存有四个区域(堆区、栈区、元数据区、程序计数器),堆区是GC的主要目标,其余区域则不需要:
- 栈会随着线程一起销毁,方法调用完毕后,方法的局部变量会随着出栈操作自动销毁
- 元数据区存的是类对象,很少会卸载
- 程序计数器是一个单纯存地址的整数,不需要处理,会随着线程一起销毁
三、垃圾标记
Java对于垃圾对象的识别较为保守,需要最大程度的避免误杀,释放不及时不太重要。一个对象如果后续再也不用,就可以认为是垃圾。Java中使用一个对象只能通过引用,而想要知道一个对象是否有引用指向,可以使用引用计数和可达性分析这两种方法来表明。
1、引用计数
(1)、定义
引用计数是给对象安排一个额外空间用来保存一个整数,表示该对象有几个引用指向。当引用增加时,计数器增加;引用销毁时,计数器减少;计数器为0时,则可认为没有引用,该对象就是垃圾。
注:引用计数法实现简单,判定效率较高,Python、PHP等语言就采用引用计数法进行内存管理。但在主流的JVM中没有使用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的循环引用问题。
(2)、缺陷
- 浪费内存空间
- 存在循环引用的情况,会导致引用计数的判定逻辑出错
当a和b销毁了,两个对象的引用计数各自减一。此时两个对象引用计数都是1,不能作为垃圾但是却不能使用,因此陷入了一个逻辑循环。
2、可达性分析
(1)、定义
可达性分析是把对象之间的引用关系理解成一个树型结构,从一些特殊的起点触发并进行遍历。能遍历访问到的对象就是可达,不可达的就是垃圾。
可达性分析进行上述遍历的起点:
- 栈上的局部变量(每个栈的每个局部变量都是起点)
- 常量池中引用的对象
- 方法区中静态成员引用的对象
总的来说可达性分析就是从所有的的起点出发,查看该对象里通过引用能访问哪些对象,把所有可以访问的对象都遍历一遍,同时把对象标记成可达,剩下的就是不可达的垃圾。
(2)、缺点
虽然可达性分析克服了引用计数的缺点但也存在问题:
- 消耗更多时间:扫描过程中需要消耗时间,当某个对象成为垃圾时,不一定能第一时间发现
- 需要暂停工作(STW问题):在进行可达性分析时需要让其他业务线程暂停工作,防止当前代码中的对象的引用关系发生变化
四、垃圾回收过程
1、标记清除
(1)、定义
标记清除算法是最基础的收集算法,算法分为“标记”和“清除”两个阶段:首先标记出所需回收的对象,然后在标记完成后统一回收所有被标记的对象。
(2)、缺点
- 效率问题:标记和清除这过程的效率不高
- 空间问题:标记清除后会产生大量不连续的内存碎片,申请空间都是申请的整块连续的空间,此处的空闲空间是离散的独立空间,因此虽然总的空闲空间充足但是无法申请到足够空间
2、复制算法
(1)、定义
复制算法是为了解决标记清理的效率问题,它将内存划分为大小相等的两块,每次只使用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上,然后再把已经使用过的内存区域一次清理掉。每次都是对整个半区进行内存回收,内存分配时不需要考虑内存碎片等复杂情况,只需要移动堆顶指针按顺序分配即可,算法实现简单运行高效。
(2)、缺点
复制算法虽然解决了内存碎片的问题但也有缺点:
- 内存利用率较低
- 如果当前的对象大部分都是要保留——垃圾很少,此时复制成本就较高
3、标记整理
(1)、定义
标记整理算法的标记过程与标记清除过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动。这类似于顺序表删除中间元素,有一个搬运的过程,将不是垃圾的对象往前搬运,并把垃圾区域统一释放。
(2)、缺点
虽然解决了内存碎片问题,但是搬运开销较大。
4、分代算法
(1)、定义
分代算法和上述的三种算法不同,其是通过区域划分来实现不同区域和不同的垃圾回收策略,从而实现更好的垃圾回收。当前JVM垃圾收集都采用的是分代收集算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。
Java堆分为新生代和老年代:
- 新生代:一般创建的对象都会进入新生代。在新生代中每次垃圾回收都有大批对象死去,只有少量存活,因此采用复制算法
- 老年代:大对象和经历了 N 次(一般情况默认是 15 次)垃圾回收依然存活下来的对象会从新生代移动到老年代。而老年代中对象存活率高,没有额外空间对它进行分配担保,因此必须采用标记清理或标记整理算法
(2)、回收策略
给对象设定年龄概念,用来描述这个对象存在多久。如果一个对象刚诞生就认为是0岁。每经过一次可达性分析,如果没被标记成垃圾该对象就增加一岁。通过年龄来区分这个对象的存活时间。如果一个对象存活时间很长,它将继续存在更长的时间。
- 新创建的对象放到伊甸区,当垃圾回收扫描到伊甸区后,绝大部分对象都会在第一轮GC中被清理,大多数对象都活不过一岁
- 如果伊甸区的对象熬过第一轮GC,就可以通过复制算法拷贝到生存区。生存区分成大小均等的两半,一次只使用其中的一半。垃圾回收扫描一半的生存区对象,发现垃圾就淘汰,不是垃圾的通过复制算法,复制到生存区的另一半
- 当这个对象在生存区熬过若干轮GC后,年龄增长到一定程度,就会通过复制算法拷贝到老年代
- 进入老年代的对象年龄都很大,再消亡的概率比前面新生代对象小不少,针对老年代的GC的扫描频次会降低很多。如果老年代中发现某个对象是垃圾,就使用标记整理的方式清除
- 特殊情况如果对象非常大,直接进入老年代(大对象直接使用复制算法)
五、垃圾回收器
- Serial收集器:新生代收集器,串行GC
- ParNew收集器:新生代收集器,并行GC
- Parallel Scavenge收集器:新生代收集器,并行GC
- Serial Old收集器:老年代收集器,串行GC
- Parallel Old收集器:老年代收集器,并行GC
- CMS收集器:老年代收集器,并发GC
- G1收集器:唯一一款全区域的垃圾回收器