GC类型分为三种:
Yong GC
Old GC
MIXED GC
Full GC
(有些文章和书上也叫Minor GC、Major GC、Full GC,由于Major GC的意思有些混淆,所以改成Old GC容易理解)
GC回收的“无用的类”(元数据区):
1、该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例
2、加载该类的 ClassLoader已经被回收
3、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
JVM性能调优主要目的是避免Full GC的发生
Full GC的慢,其实是相对于Yong GC和Old GC。
Yong GC回收新生代
Old GC回收年老代,个别JVM参数配置设置了Old GC之前必须执行一次Yong GC
MIXED GC回收新生代+老年代,G1回收算法
Full GC回收新生代、老年代、元数据区/永久代
Full GC为什么那么慢?
1、元数据区的回收算法效率低,虚拟机规范Class回收条件比较苛刻
2、Full GC回收新生代、老年代、元数据区/永久代。从这个角度讲,多回收了方法区,增加了总的回收耗时。(有些文章提到Full GC能回收堆外直接内存,这个说法并不准确,本质上是JVM一个后台线程,通过虚引用遍历堆里已经被回收的对象对直接内存的直接引用,显示的调用free()方法释放直接内存,Full GC不能直接释放堆外直接内存。总之,堆外直接内存的释放,和GC方式类型无关)
3、Full GC本身不会先进行Minor GC,我们可以配置-XX:+ScavengeBeforeFullGC(非CMS回收算法)、CMSScavengeBeforeRemark(CMS回收算法)可以,让Full GC之前先进行一次Minor GC,因为老年代很多对象都会引用到新生代的对象,先进行一次Minor GC可以提高老年代GC的速度。
(G1回收算法除外,G1回收有一个MIXED回收阶段,新生代和老年代都一起回收,与配置的JVM参数无关)
4、CMS发生了concurrent mode fail,young区使用ParNew(并行GC),Old+Perm(单独设置)使用CMS,整个堆(young+old+metaspace/perm)使用MSC(Mark Sweep Compact)是CMS GC算法的Full GC算法,单线程回收整个堆,回收过程有严格的步骤,碎片压缩,它是单线程的标记-压缩收集器,所以耗时非常的长。
(单线程、碎片压缩,由于并发失败,不能继续并发回收,STOP-THE-WORD回收效率是必要的,系统的内存很紧张了,好多新对象在着急的等着分配内存,不允许你再慢慢的并发回收内存,但这意味着停顿时间的大幅度增加)
5、G1发生了concurrent mode fail之后退化成了单线程回收整个堆
为什么老年代垃圾回收效率比新生代低很多?
为什么Yong GC比Old GC慢?为什么Minor gc速度比Major GC慢?
这里的Yong GC=Minor gc,Old GC=Major GC
Old GC的速度一般会比yong gc慢10倍以上
1、从并行和并发机制,并行和并发的默认线程数上,可以看出-XX:ConcGCThreads=(-XX:ParallelGCThreads + 3)/ 4
(XX:ParallelGCThreads是并行线程数,XX:ConcGCThreads是并发线程数)
(并行是STOP-THE-WORD,并发是回收垃圾的时候,应用查询继续在执行)
2、(时间换空间s1、s2)新生代复制算法比较快。Eden区回收时直接全部清空,存活的对象存放到内存容量比较小的s1,少了解决内存碎片整理 加上直接copy的速度,效率很高。
3、(新生代GC根据卡表,只需扫描部分老年代)卡表数据库结构,卡表为一个比特位的集合,卡表中每一位表示老年代4KB的空间,每一个比特位可以用来表示老年代的某一区域中的所有对象是否持有新生代对象的引用。
这样新生代GC时,可以不用花大量时间扫码所有老年代对象,来确定每一个对象的引用关系,
而可以先扫码卡表,只有卡表的标记位为1时,才需要扫码给定区域的老年代对象
而卡表位为0的锁住区域的老年代对象,一定不含有新生代对象的引用
4、(空间换时间),老年代标记清除算法会导致内存碎片化,因此就引入了标记整理算法,执行完毕后,存活的对象会按序放置,移动对象的内存地址(重点),来解决碎片化,但是执行时间较长。
5、老年代区内存容量一般较大,回收需要预留比较大的空间(老年代GC是并发执行,重新标记的时候修正引用关系),这样的话内存利用率就低(相对于新生代利用率eden+from=90%,新生代存活率低不需要预留太多内存)