先来介绍几种经典的回收算法:
- 标记-清除算法(Mark and Sweep)
如同名字,将回收分成两个阶段:标记和清除,使用的算法主要是可达性算法。
标记:从根集合进行扫描,对存活的对象进行标记。
清除:对堆内存从头到尾进行线性遍历,回收不可达对象内存。
可达性算法就不多说了,从图中来看,当我们回收了一些区域块的时候,这些空出来的地方,将会形成内存碎片,表面上看起来不算什么,但是当我们要运行一个比较占内存的进程的时候,这些碎片就会将内存浪费掉,触发针对碎片的垃圾回收,最后导致OOM。
- 复制算法(Copying)
复制算法:将容量,按照一定比例分为对象面和空闲面,并且将对象在对象面上进行创造,其他的则作为空闲面,当盛放对象的对象面占用的内存用完了之后,就将还存活着的对象复制到空闲面上,然后再把原来的对象面进行内存清除。
这种算法,适用于对象成活率低的场景,比如新生代这种。但是就不用考虑内存碎片了,直接将那些对象从堆顶到堆底利用指针去进行逐一复制即可。
在分代收集算法的新生代中,算是应用频率比较高的。
以上的两种算法,都可以去应对成活效率不高的对象,但是如果成活率很高的话,我们就要更换垃圾回收算法了。
- 标记-整理算法(Compacting)
标记整理算法算的上是一种标记-清除算法的升级版了,利用更高的计算成本,将碎片化的问题解决了。
- 标记:从根集合进行扫描,对存活的对象进行标记
- 清除:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。
该算法:
(1). 避免内存的不连续性
(2). 不用设置两块内存互换
(3). 适用于存活率高的场景,例如分带收集算法的老年代。
- 分代收集算法
分带收集算法可以说是垃圾回收算法的组合拳。它会按照对象生命周期的不同划分成为不同的垃圾回收算法去进行垃圾回收。但最终的目的也还是提升垃圾回收的效率。
在JDK6和JDK7中,Java的分代收集分为新生代,老年代和永久代。
到了JDK8开始到现在,都没有永久代了,只剩下了新生代和老年代。
因为新生代的存活率低,所以就会采用复制算法,因为老年代的存活率高,所以会使用标记整理算法。
分代收集算法的GC分为两种GC:
- Minor GC
Minor GC是新生代中的垃圾收集动作,所采用的就是刚才提到的复制算法,新生代是几乎所有java对象出生的地方,也就是说,java对象的诞生和他们申请内存都是在这个地方展开的,因为java 本身的大部分对象都不会存活太多时间,所以在新生代的内存区域中会频繁的进行垃圾回收。
新生代:尽可能快速的收集掉那些生命周期短的对象。
新生代又分为Eden区和两个Survivor区.
Eden区
Eden区,代表java对象刚被创建出来,其内存将会被首先置放在Eden区,但如果Eden区放不开这些被创建出来的对象的话,就会可能被放在Survivor区。
Survivor区
两个Survivor区,分别被定义为from区和to区。而至于谁是from区谁是to区,还要伴随着垃圾回收的进行,不断的进行更替。
新生代回收垃圾的过程如下
正常情况下,一般都是这样去回收的:
首先对象被创建出来,进入Eden区,假如Eden区进去了4个对象,经过一波回收,三个都被回收了,一个活了下来,那么活下来的就进入了Survivor0,也就是from区。
紧接着,又一批对象被创建出来进入了Eden,又经过一轮垃圾回收,进来了四个剩下了两个,这时候,Eden区的2个和Survivor0区的1个,将会进去to区,然后to区将会改名为from区。
然后就是不断的轮回。
那么,新生代的对象将如何转变为老年代呢?
首先,这个对象需要经历一定Minor次数并且依然存活(默认是15代)。
如果有特殊情况,比如说,Eden区和Survivor区都放满了,也会进入老年代;又或者说,一个巨大的对象被创造出来了,也会进入老年代。
而前面的这些也实际上都有一些临界参数可以去调整
-XX:SurvivorRatio:Eden和Survivor的比值,默认为8:1
-XX:NewRatio:老年代和新生代内存大小的比例
-XX:MaxTenuringThreshold:对象从新生代代晋升到老年代经过GC次数最大的阈值。
- Full GC
Full GC 与老年代直接相关,在老年代中,经常所使用的垃圾回收算法是 标记-清理算法或标记-整理算法。
由于老年代与新生代息息相关,所以当老年代启动了回收算法的时候,也多多少少会影响到新生代的对象,也会对新生代的垃圾或内存进行回收。
Full GC的特点
Full GC比Minor GC慢,但是执行效率低,毕竟是从Survivor区熬过来的。
触发Full GC的条件
- 老年代满了,空间不足,触发Full GC
- (jdk6及之前)永久代空间不足
- CMS GC时出现promotion failed,concurrent mode failure
- Minor GC晋升到老年代的平均大小大于老年代的剩余空间。
- 手动GC,调用System.gc()
- 使用RMI(远程方法调用)来进行RPC或者管理的JDK应用,每小时执行1次 Full GC