目录
一、判定对象是否存活算法
(1)引用计数算法
在对象中添加一个引用计数器,每有一个引用,计数器就就+1;引用失效,计数器就-1。当计数器为零时则判断该对象已死。
- 缺点:难以解决对象相互引用的问题。
(2)可达性分析
将GC Roots作为根节点,如果一个对象到根节点不可达时,就认定该对象已死。
二、引用类型
- 强引用:垃圾回收不会回收掉
- 软引用:还有用,但不是必须的对象。内存不够时,会回收
- 弱引用:非必须的对象。无论内存够不够,都会回收
- 虚引用:唯一目的是垃圾回收时收到一个系统通知。
三、常见垃圾回收算法
(1)标记-复制算法
将内存分成大小相等的两块,每次只使用其中一块。当触发垃圾回收时,将还存活的对象复制到空闲的内存块中,然后把使用过的内存块清理掉。
- 缺点:内存缩小为原来的一般,空间浪费。
(2)标记-清除算法
首先标记出需要清理的对象,然后统一回收掉被标记的对象。
- 缺点:内存空间碎片化。执行效率不稳定。
(3)标记-整理算法
首先标记出需要清理的算法,之后向内存的一端移动,然后清理掉。
优化:大多数时间使用标记-清除算法,直到内存碎片影响对象分配内存空间时,执行一次标记-整理算法。
- 缺点:相较标记-清除算法更加复杂。
(4)分代收集算法
不同的内存区域,采取不同的垃圾回收算法。
- 新生代:使用标记-复制算法。每次分配内存只使用Eden区和一块幸存者区,当发生垃圾回收时,将这两块内存中存活的对象复制到另一块空的幸存者区中,然后清理掉这两块区域。
- 老年代:使用标记-清除和标记-整理。
四、常见垃圾收集器
(1)Serial
- 单线程
- 新生代
- 标记-复制
- 在垃圾收集时,会暂停用户线程
- 默认新生代收集器,简单高效
(2)Serial Old
- 单线程
- 老年代
- 标记-整理
- 在垃圾收集时,会暂停用户线程
(3)ParNew
- 多线程
- 新生代
- 标记-复制
(4)Parallel Scavenge
- 多线程
- 新生代
- 标记-复制
- 吞吐量优先收集器
- 吞吐量 = 代码运行时间 / 代码运行时间 + 垃圾收集时间
(5)Parallel Old
- 多线程
- 老年代
- 标记-整理
(6)CMS
- 多线程
- 老年代
- 标记-清除
- 回收停顿时间优先
- 四个步骤
(1)初始标记:标记GC Roots直接关联对象,需要停顿
(2)并发标记:从直接关联对象遍历整个对象图,不需要停顿
(3)重新标记:标记并发标记期间产生的需要标记的对象,需要停顿
(4)并发清除:清理标记对象,不需要停顿
劣势:
(1)并发会降低吞吐量
(2)产生内存碎片
(7)G1
- 多线程
- 不限定新生代和老年代
- 四个步骤:
(1)初始标记:标记GC Roots直接关联对象,需要停顿。与Minor GC同步,实际没有额外停顿。
(2)并发标记:从直接关联对象遍历整个对象图,不需要停顿
(3)最终标记:标记并发标记期间产生的需要标记的对象,需要停顿
(4)筛选回收:移动标记对象并清理,需要停顿
五、GC收集
- 新生代收集(YGC,MinorGC):只是新生代的垃圾回收
- 老年代收集(OGC,MajorGC):只是老年代的垃圾回收,只有CMS GC有这种行为。很多时候MajorGC和FullGC会混淆使用。
- 混合收集(Mixed GC):收集整个新生代和部分老年代。只有G1 GC有这种行为
- 整堆收集(FullGC):收集整个堆空间和方法区
五、内存分配策略
- 对象优先在 Eden 区分配
- 大对象直接进入老年代
- 长期存活的对象将直接进入老年代(默认15次Minor GC后)
- 动态对象年龄判断:若幸存者空间中相同年龄对象的和大于幸存者空间的一办,那么大于等于这个年龄的对象就可以移入老年代
- 空间分配担保:Minor GC后大量对象存活,将幸存者区无法容纳的对象送入老年代。