垃圾回收算法分类:
- 标记-清除算法
- 复制算法
- 标记-整理算法
标记清除算法
顾名思义,该算法进行垃圾回收共分为两个阶段:
- 标记: 从根集合出发,进行扫描,通过可达性算法对
需要回收的对象进行标记
.
这里有的地方说的是对存活的对象进行标记,其实也有道理,不过书上说的是对需要回收的对象,那就写需要回收的对象
- 清除: 对堆内存从头到尾进行线性遍历,回收不可达对象内存.
标记清除算法的不足:
- 由于标记-清除算法不需要进行对象的移动并且仅对不存活的对象进行处理,所以回收后,会
产生很多不连续的碎片化的空间
,这就有可能导致较大的对象没有足够的内存存放,因此不得不提前触发另一次垃圾回收. - 效率比较低.
复制算法
- 将可用的内存按容量及一定比例划分为两块, 并选择其中
一块作为对象面
,另一块作为空闲面
. - 对象在对象面上创建.
- 当对象面的内存用完之后,就将
存活的对象从对象面复制到空闲面
. - 将对象面的所有对象内存全部清除.
- 这种方法
适用于对象存活率较低
的场景. - 移动的时候,只需要移动堆顶指针,按顺序分配内容即可.
复制算法的好处:
- 由于每次是对一整个区域进行回收,所以不会产生不连续的碎片化的空间.
- 移动的时候直接顺序分配内存,简单高效.
- 年轻代的垃圾回收主要通过这种算法.
标记-整理算法
当复制算法在对象存活率比较高的时候就要进行较多的复制操作,效率就会变低,而且,如果不想五五开这样浪费50%的空间的话,就需要额外空间进行担保
,防止空闲面的内存不够存活对象放的情况,因此,老年代不采用这种算法,老年代采用标记-整理算法.
标记-整理算法过程:
- 标记: 从根集合进行扫描,通过可达性分析算法,对存活的对象进行标记.
- 清除: 清除时移动所有存活的对象,且
按照内存地址次序
依次排列,然后将末端内存地址以后的内存全部回收.
标记-整理算法好处:
- 解决了碎片化空间的问题.
- 不需要像复制算法一样设置两块内存.
- 适合于对象存活率较高的场景.
分代收集算法(主流GC算法)
分代收集算法可以认为是垃圾回收算法的组合拳
它将堆内存进行进一步的划分,按照对象生命周期的不同来划分区域以采用不同的垃圾回收算法.
这样的目的就很简单: 用于提高JVM的回收效率
.
一般来说,年轻代存活率低,就采用复制算法,老年代存活率高,就采用标记-清除算法或者标记-整理算法.
GC的分类
分代收集算法的GC主要有两种:
- Minor GC: 也就是发生在年轻代的垃圾收集动作,所采用的的是复制算法,年轻代几乎是所有java对象出生的地方, 即java对象申请的内存以及对象的存放都在这里进行.
- Full GC: 该GC与老年代相关,由于
对老年代的回收一般会伴随着年轻代的垃圾收集
,因此它才被称为Full GC.
JDK7及其之前的堆内存分布图:
由图可知,JDK7及以前,堆内存可以分为三个部分: 年轻代
,老年代
,永久代
.
JDK8及其以后的堆内存分布图:
如图所示: JDK8及其以后,由于没有了永久代,所以堆内存就只有两部分了:年轻代
,老年代
.
年轻代
年轻代中存放的都是那些朝生夕灭
的对象,所以它会尽可能快速地收集掉那些生命周期短的对象,主要包括三个区域:
- Eden区: 伊甸园啊~~~,所以说我们就可以知道对象刚被创建出来的时候,都是被放在Eden区的,当然了,Eden放不下了还是有可能放在Survivor区的.
- 两个Survivor区(from区, to区): 这两个区域不是固定的,会随着垃圾回收相互转换.
- 三个区域的比例如图:
在年轻代中,每次它都只会使用Eden区和其中的一块Survivor区(一般认为是From Survivor区)用来存放对象,当进行GC的时候,它就把这两个区域里存活的对象全部都放到另一个Survivor区域(To Survivor区)中去
,然后一次性清除掉之前的Eden区和From Survivor区,最后交换两个Survivor区的身份
,将From Survivor当作To Survivor区,同时注意:当survivor区域的内存不足以存放所有的存活对象的时候,就会往老年代中放
.
老年代
老年代中存放的一般都是生命周期较长的对象,采用标记-清除算法或者标记-整理算法进行垃圾回收.
由于对老年代的回收一般会伴随着年轻代的垃圾收集,也就是说会对整个堆进行回收
,所以它被称为Full GC或Major GC.
Full GC比Minor GC慢,但是执行频率较低.
触发Full GC的条件
- 老年代空间不足.
- CMS GC(并发标记-清除算法)时出现promotion failed(minor gc时survivor中放不下了,对象要放到老年代,同时此时老年代也放不下), concurrent mode failure.
- Minor GC晋升到老年代的平均大小大于老年代剩余空间.
- 调用
System.gc()
,此时会提醒虚拟机要对老年代和新生代进行回收,但是具体啥时候回收,要不要回收还是由虚拟机决定.
对象如何晋升到老年代
- 经历一定的Minor GC次数之后依然存活的对象(
对象在新生代每经历一次Minor GC,它的年龄计数器就会加1,一般年龄计数器到15了就会进入老年代
). - Survivor区中存放不下的对象会直接进入老年代.
- 新生成的大对象直接进入老年代中(-XX: +PretenuerSizeThreshold控制大对象的大小,超过该值的就直接进入老年代).
常见的调优参数:
比如当值设置为8的时候,就表示Eden与其中一个Survivor的比例为8:1
.
比如当值设置为2的时候,那么老年代的内存和年轻代内存的比例为2:1
.
新生代和老年代所占内存的总大小是通过-Xmx和-Xms
决定的.