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