浅谈JVM的垃圾回收
如何确定垃圾
如何确定对象是否是垃圾,是否可以被回收。jvm引入了GC Roots。如果某个对象和GC Roots没有直接或者间接的引用关系,那么这个对象被视为垃圾。那有哪些可以作为GC Roots的呢?有:
类静态属性中引用的对象
常量引用的对象
虚拟机栈中引用的对象
本地方法栈中引用的对象
垃圾回收算法
标记-清除算法
该算法会从每个GC Roots出发,标记那些有被引用的对象,最后将没有被引用的对象清除。
这个算法会带来内存碎片问题,用在旧生代。
标记-整理算法
该算法是为了解决标记-清除带来的内存碎片问题。
首先一样从每个GC Roots出发,标记那些有被引用的对象,然后将这些存活的对象移到内存的一端,最后再将没有引用的对象清除。
该算法由于多了一步整理的步骤,所以速度会比标记-清除算法慢,也是用在旧生代。
复制算法
改算法为了能够并行的标记和整理,将内存空间为为两块,一块上面有对象,另一块是空的。有对象的那块被激活,将上面的是存活的对象复制到另一块,并且整理好。然后未激活的被激活,原本已激活的标记为未激活,同时清理上面的对象。这种算法速度比较快,用于新生代。新生代由1个Eden区和2个Survivor(Survivor From,Survivor To)区组成。每个发生GC,会把Eden和Survivo Fromr分为一块,Survivor To分为一块(空的)。把Eden和Survivor组合的那一块的对象复制到Survivor To,然后清除Eden和Survivor From。接着Survivor From会变成Survivor To,Survivor To会变成Survivor From用于下次GC。
垃圾收集器
两个概念
YGC:Young GC,当新生代容量不足时触发,产生于新生代的垃圾回收
FGC:Full GC,当旧生代容量不足时触发,新生代和旧生代产生垃圾回收
在GC过程中,会产生STW(Stop the World),即在垃圾回收的某个阶段会暂定整体应用的运行。现阶段任何垃圾收集器都没法消除STW现象,而随着垃圾收集器的发展,STW的时间也越来越短。
垃圾收集器如何有十多种,并且有很多都是分代垃圾收集器,在介绍时一般就按照成对老介绍。这里挑常见的几种。
Serial 和 Serial Old
最早的垃圾收集器,在JDK诞生的时候就存在。其中Serial用于新生代,Serial Old用于旧生代。
都是采用单线程的方式进行。在单CPU的情况下,由于没有线程切换的开销,专心做垃圾清除,所以单线程情况下效率最高。但是随着机器内存的增多,这类回收器产生STW时间相对较长。随着JDK的发展,该类垃圾回收器已经不是默认的垃圾回收器了。如图
Parallel Scavenge 和 Parallel Old
JDK1.7 JDK1.8默认的垃圾回收器。在生产环境下,如果没有专门指定,大多数也这两个垃圾回收器(毕竟大多数公司用的还是JDK1.8)。Parallel Scavenge(以下简称PS)是新生代的垃圾回收器, Parallel Old(以下简称PO)是旧生代的垃圾回收器。两者常用的多线程的方式进行GC的。因此,它们清理的速度要比Serial快多了,STW产生的暂停也更短了。
ParNew 和 CMS
ParNew可以看做是Serial的多线程版本,也是新生代的垃圾回收器,是为了配合CMS。CMS是旧生代的垃圾回收器。CMS的全称Concurrent Mark Sweep Collector。虽然它自身有局限性,但它在垃圾回收器历史上具有里程碑意义,这是一个并发的垃圾回收器。之前的垃圾回收器在GC时,只会有GC线程在运行,而CMS能够GC线程和应用线程同时运行。CMS的垃圾回收示意图如下:
第1、3步还是会引发STW,不过比较耗时的是2、4步。因此,CMS的整体STM的停顿时间更短。但是由于CMS采用的是标记-清除算法,会产生大量的内存碎片。当旧生代的内存碎片过多而没有存储对象时,CMS会被替换成Serail Old进行标记-整理。不过由于Serail Old的局限性,产生的STW的停顿时间会比较长。总的来说,CMS的回收停顿时间比较短,是比较好用一种垃圾回收器。
G1
G1是JDK1.9默认的垃圾回收器。它取消了物理上的分代,只有逻辑上分代。和CMS相比,G1具备压缩功能,能够避免碎片问题,STW的暂停时间更短。
G1将java内存分为多个大小相同的区域,包括Eden,Survivor,Old,Humongous。Humongous是特殊的Old类型,专门放置大型对象。这样划分使得内存不再是物理上的分代,而是逻辑上的分代。G1将会优先回收垃圾最多的区域,因为采用的是Mark-Copy算法,即复制算法,有良好的空间整合能力。