1.可达性分析
Java从一系列的GC roots作为头节点,向下遍历,不在其路径(引用链)下的对象就是不可达的。GC roots可以是方法区的类静态属性引用的对象,或是方法区常量的引用对象,以及虚拟机栈和本地方法栈的引用对象。所有的GC roots都存放在一个叫OopMap的数据结构中。
2.JVM的垃圾回收算法
2.1 分代回收算法
与其说是一种算法,它更像是一种思想,它根据对象存活的时间将内存划分为老年代和新生代, 对于两者分别用合适的垃圾回收算法;
2.2 标记清除算法
发生在老年代,标记清除算法会遍历两次,第一次标记需要回收的对象,第二次进行清除,使用后会产生内存碎片;
2.3 复制算法
多在新生代使用的算法,将存活的对象拷贝到另一个空间中,然后直接将原来的空间清空。 这样做执行效率高,还可以避免产生内存碎片。
在实际应用中,由于新生代的对象大多数都会被回收,所以将空间分为一个Eden区(占80%)和两个Survivor区(各占10%), 每次使用一个Eden区和一个Survivor区来储存对象,每次垃圾回收时,将存活的对象拷贝到另一个Survivor区,如果Survivor区的空间不足,会占用老年代的空间;
2.4 标记整理算法
发生在老年代中,会遍历两次,第一次遍历标记将要回收的对象,第二次将存活的对象全部朝一端移动,然后清空边界以外的内存;这种方法使用后不会产生内存碎片。
3.Minor GC和Full GC的触发
Minor GC的触发:
当JVM无法为新的对象分配空间时会触发Minor GC,例如当Eden区和Survivor区满了的时候,默认情况下,新生代的对象在经历15次Minor GC后进入会老年代;
Full GC的触发:
1.调用System.gc()方法会建议系统执行Full GC;
2.当老年代空间不足时会触发Full GC,如果频繁发生Full GC,建议调整新生代和老年代的比例, 让对象尽量在Minor GC阶段被回收,并且尽量不要创建过大的对象,以及让对象在新生区存活的久一点。
3.当方法区空间不足时也会触发Full GC;
4.垃圾回收器
4.1 Serial收集器(不用)
单线程的收集器,在工作时必须停止所有工作线程(stop the world),使用复制算法;
4.2 Serial Old
和Serial一起使用,分别作用于新生代和老年代,使用标记整理算法;
4.3.ParNew
使用多线程进行垃圾回收,在工作时必须停止所有工作线程,和CMS配合使用;
4.4.CMS(并发标记清除)
是一种以获取最短回收停顿时间为目标的收集器,重视服务器的响应速度,希望系统停顿时间虽短,适合堆内存大,CPU核数多的服务器端应用,Serial Old作为CMS出错后的备用收集器。
步骤:初始标记(需要STW),并发标记,重新标记(需要STW),并发清除,初始标记只标记GC Roots可以关联到的对象,并发标记负责对GC Roots进行追踪,标记存活的对象,重新标记负责标记在并发标记过程中标记信息发生变动的对象,并发清除负责回收;
优点:
并发收集停顿低;
缺点:
1.对CPU资源压力大;
2.会产生内存碎片,其默认会在需要时对内存碎片进行合并整理,该过程会STW,用户也可以设置在一定次数的Full GC后执行一次带压缩的Full GC;
3.需要额外预留一部分内存空间给用户,所以当老年代还有一定的空间时就会触发,因为它的垃圾清理是和线程并发进行的,清理的过程中还会产生内存垃圾, 这些垃圾被称为浮动垃圾,如果浮动垃圾过多就会启用serial oid收集器来处理,从而产生较大的停顿;
4.5 Parallel Scavenge(默认使用)
新生代的垃圾回收器,吞吐量优先的垃圾回收器,用户可以自行设置吞吐量的大小和最大垃圾回收时间, 使用多线程进行垃圾回收,使用复制算法收集,和Parallel Old配合使用;
4.6 Parallel Old
老年代的垃圾回收器,使用多线程进行垃圾回收,使用标记整理算法
4.7 G1
不再分新生代和老年代,而是将整体划分成多个子区域,整体上采用标记整理算法,局部上采用复制算法,不会产生内存碎片,STW时间可由用户自己设置。一些区域被划分为老年代,当对这些区域清理时,直接将对象复制到另一个区域,就不会产生内存碎片,其运行阶段大致可分为:初始标记,并发标记(不需要STW),最终标记,筛选整理。 与CMS相比的优势:没有内存碎片,可以控制停顿时间