gc算法
1 gc算法
gc的对象是堆空间和永久区的不可触及的对象。
*对象有三个状态 可触及:从根节点可以触及到这个对象;可复活:引用被释放,但是finalize方法中可复活;不可触及:引用释放,finalize不复活。注意,finalize只会在第一次垃圾回收的时候调用一次,以后不会再掉用,而且调用时间不确定,避免使用finalize防止对象永远存在。
*根节点:栈中引用的对象,方法区静态成员(全剧对象)可以作为根节点。
(1)引用计数法 通过引用计数计算可达性来回收垃圾,为每个对象标注使用数量,引用可达则加一,引用失效则减一。从根对象算起,引用可达则为有效对象。缺点:引用和去引用伴随加减,影响性能;解决不了孤岛问题,即循环引用问题。java没有使用引用计数法。
(2)标记清除 主流垃圾回收的思想基础。可能产生内存碎片,内存空间不连续。
标记阶段:从根节点标记所有可达对象。
清除节点:清除所有未被标记或标记为垃圾的对象。
(3)标记压缩 适用于存活对象较多的场景,如老年代,在标记清除算法的基础上做了优化。比标记清除的优点在可用内存更有序有连续内存。
以标记清除为基础,把所有表的的存活对象压缩到内存一端,然后清除边界外所有空间。
(4)复制算法 相对于标记清楚较为高效,不适合存活对象较多的场合如老年代,主要用于新生代。
将原有内存空间分为相等大小的两块,每次只用其中一块,垃圾回收时将正在使用的内存中的存活对象复制到未使用的内存中,然后清除正在使用的内存中的所有对象。缺点是只能用一半内存,资源浪费。
实际作用时,JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1:1。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次GC,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中。在GC开始的时候,对象只会存在于Eden区和From区,To是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到To,而在From区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到To区。经过这次GC后,Eden和From已经被清空。这个时候,From和To会交换他们的角色。GC会一直重复这样的过程,直到To被填满,To被填满之后,会将所有对象移动到年老代中。
*分代思想
新生代多为短命对象,gc有少量对象存活,适合复制算法。
老年代多为长期对象,gc会有大量对象存活,适合标记清除或标记压缩。
*stop-the-world java中一种全局暂停的现象,多半由gc引起,也可能由dump线程、死锁检查、堆dump等引起。gc边清理系统边产生,垃圾清理不完,只有停止系统所有活动才能清理完。这种现象难以避免,新生代gc停顿比较短,老年代gc可能很长。
2 gc种类
minor,major/full
*Minor GC 新生代gc。 当jvm无法给一个新对象分配空间时会出发,即eden满了触发,不会影响到永久代,会触发stop-the-world,由于新生代对象存活率低,所以暂停不会太久。
*Full GC 老年代或永久代空间不足触发。还有一种情况,为了避免新生代转入老年代导致老年代空间不足,在minor gc时,如果minor gc转入老年代的平均大小大于老年代剩余的内存,那么也会触发。
3 gc收集器
(1)串行收集器,古老稳定高效的收集器,但是停顿时间比较长,因为只使用一个线程进行回收。
-XX:+UserSerialGC ,新生代使用复制算法,老年代使用标记压缩算法。
(2)并行收集器 ParNew 只在新生代并行。
-XX:+UseParNewGC,新生代并行,老年代串行,新生代还是复制算法,老年代标记压缩。多核cpu会比较快,单核建议串行收集器
-XX:ParallelGCThreads: 线程数量
(3)并行收集器 Parallel收集器
更加关注吞吐量的收集器,新生代还是复制算法,老年代标记压缩.
-XX:+UseParallelGC 新生代并行,老年代串行。
-XX:+UseParallelOldGC 新生代老年代都并行。
(4)cms收集器 concurrent mark sweep 并发标记清除
使用标记清除算法,与应用程序线程一起执行,停顿会减少一些,吞吐量也会降低。适用于老年代,新生代还是用parnew。
-XX:+UseConcMarkSweepGC
*-XX:MaxGCPauseMills 最大停顿时间,gc尽量不超过,但不保证。
*-XX:GCTimeRatio 垃圾收集时间跟总时间占比,默认99 即1%时间gc。
cms运行分为四步,其中有两步是并发两步单线程,尽可能缩小全局停顿时间但不能完全避免,分别为
*初始标记,从根节点关联到对象,停顿不并发,(CMS-initial-mark)
*并发标记,在程序运行过程中标记全部对象,和用户线程一起。(CMS-concurrent-mark)
*重新标记,在正式清理前再做修正,停顿不并发。(CMS-remark)
*并发清除,基于标记结果直接清理对象,和用户线程一起。(CMS-concurrent-sweep)
*并发重置,并发重设状态等待下次CMS的触发(CMS-concurrent-reset)
由此可见,cms会尽可能降低停顿,但清理不彻底,因为是并发的所以应用会一直有垃圾,也是因为并发,所以不能在空间快满时再清理。如果清理速度赶不上应用内存申请速度,会报 concurrent mode failure,此时应该使用串行收集器,暂停应用来回收。
*照顾到不断并行产生的新对象,cms用的标记清除不是标记压缩。所以会产生内存碎片。可以配置参数
-XX:+ UseCMSCompactAtFullCollection fullgc后进行一次整理。
-XX:+ CMSFullGCsBeforeCompaction 设置几次fullgc后进行一次碎片整理
-XX:ParallelCMSThreads cms线程数量
4 如何减轻gc压力
系统架构设计,代码编写,堆空间分配。