一、垃圾标记算法
要回收垃圾,则要知道哪些是垃圾
1.引用计数算法
判断对象的引用数量,每个对象实例都有一个引用计数器,被引用+1、完成引用-1
优点:执行效率高,程序执行受影响较小
缺点:无法检测出循环引用的情况,导致内存泄漏
2.可达性分析算法
判断对象的引用链是否可达来决定对象是否可以被回收
可以作为gcroot的对象 :
虚拟机栈中引用的对象(栈帧中的本地变量表)
方法区中的常量引用的对象
方法区中的类静态属性引用的对象
本地方法栈中jni(native方法)的引用对象
活跃的线程引用对象
二、垃圾回收算法
光标记了哪些是垃圾还不行,还要将其回收
1.标记-清除算法
标记:使用可达性分析算法,从gcroot集合扫描对,对存活对象标记
清除:回收不可达对象的内存
缺点: 产生不连续的内存碎片,当需要较大的内存区域的时候,会导致内存不够再次触发gc
2.复制算法
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对 象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
优点:解决了碎片化问题,顺序分配内存简单高效,适用于对象存活率低的场景(新生代)
缺点:空间利用率不高(比如200MB的空间,实际只能用上100MB代价太大了)
3.标记整理算法
标记过程仍然与标记 - 清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。
优点:避免了内存的不连续、提高了内存的利用率、适用于对象存活率高的场景(老年代)
缺点:它对内存变动更频繁,需要整理所有存活对象的引用地址,在效率上比复制算法要差很多
4.分代收集算法
是融合上述3种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳。对象存活周期的不同将内存划分为几块。 一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
在新生代中的对象朝生夕死,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。每次 Minor GC,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次 Minor GC 时,From 与 To 职责兑换,这时候会将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复,对象Minor GC达到一定次数后,便可进入老年代。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须 使用标记-清理或者标记 - 整理算法来进行回收。
对象如何晋升到老年代:
- 经历一定Minor GC次数后依然存活的对象。虚拟机给每个对象定义了一个对象年龄(Age)计数器。每次GC年龄+1,默认15岁会被移入老年代
- Eden或Survivor区放不下时。对象先在Eden区分配空间,当Eden区放不下会触发一次Minor GC,将Eden区还存活的对象放入Survivor区,当Survivor区放不下时,由老年代做分配担保,进入老年代
- 新生的大对象。大对象指需要大量连续内存空间的对象,这部分对象不管是不是“朝生夕死”,都会直接进到老年代。这样做主要是为了避免在 Eden 区及2个 Survivor 区之间发生大量的内存复制。(-XX:+PretenuerSizeThreshold可指定大对象的大小,超过设定值直接进入老年代)
三、GC分类
Minor GC:发生在年轻代
Full GC:发生在老年代,一般会伴随年轻代的垃圾收集,所以叫full gc
触发Full GC的条件:说出至少三条
1. 老年代空间不足
2. 永久代空间不足(8以后取消了)
3. CMS GC出现promotion failed, concurrent mode failure
4. Minor GC晋升到老年代的平均大小大于老年代的剩余空间, 检查老年代空间是否小于Minor要到老年代对 象的大小, 如果小, 则只需Full GC
5. 调用System.gc(), 至于回不回收还是虚拟机来决定
6. 使用RMI来进行RPC或管理的JDK应用, 每小时执行1次Full GC