垃圾收集器
GC概念
如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
GC(Garbage Collection)的工作任务可以分为两类:内存的动态分配和垃圾回收。在内存执行分配之前,GC会先对内存进行划分区域,在上一篇博客中我们知道Java虚拟机使用了分代收集算法,所以堆区里面会被分为新生代和老年代两个区域,新生代又分为Eden空间、From空间和To空间。 空间划分完成后,GC就可以为新对象分配内存空间。
在JDK版本这么多年发展的途中,垃圾收集器也已经出现了多种版本,基于分代的概念,不同的分代空间中活动着不同的GC。
从不同的角度分析垃圾收集器,可以将GC分为不同的类型。
按照线程数分,可分为串行垃圾回收器和并行垃圾回收器。
按照工作模式分,可分为并发式垃圾回收器和独占式垃圾回收器。
按碎片处理方式分,可分为压缩式垃圾回收器和非压缩式垃圾回收器。
按工作内存分,可分为新生代垃圾回收器和老年代垃圾回收器。
下面简单介绍这几个概念
并发式回收器和独占式回收器
并发式垃圾回收器与应用程序线程交替工作,以尽量减少程序的停顿时间。
独占式回收器(Stop the world)一旦运行,就会停止应用程序中的其他所有线程,直到垃圾回收完全结束。
串行回收器和并行回收器
串行回收器是所有垃圾回收器中最古老的的一种,也是JDK中最基本的垃圾回收器之一,他的主要特点是使用单线程进行垃圾回收,其次,他是独占式的回收机制。
串行回收是指在同一时间段内只允许一件事情发生,也就是说,当多个CPU可用时,也只能由一个CPU用于执行垃圾回收操作,并且在执行垃圾回收期间,其他的工作线程将被暂停,当垃圾回收完全结束之后才会恢复之前被暂停的工作线程。
并行回收器是工作在新生代的垃圾收集器,它只是简单的将串行回收器多线程化,本质上和串行回收器一模一样。
并行回收器也是独占式,在回收过程中,应用程序的线程会全部暂停,但是由于并行回收器使用多线程进行垃圾回收,在并发能力比较强的CPU上,它产生的停顿时间要短于串行回收器,而在单CPU系统中,并行回收器的效果不会比串行回收器好。
压缩式回收器和非压缩式回收器
压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理,消除回收后导致的内存空间碎片。
没有整理空间碎片的步骤就叫非压缩回收器。
Serial 收集器
Serial收集器是历史最悠久的收集器,在早期的JKD版本中,Serial收集器是新生代唯一的选择。
它是串行的,也是独占式的,这是它的缺点,但如果限定于单个CPU的环境来说,Serial收集器由于没有线程交互的开销,自然可以获得最高的单线程收集效率。
注意:Serial收集器虽然古老,但是久经考验,在大多数情况下,性能表现是相当不错的,采用复制算法。
同时,老年代中也有Serial收集器,叫做Serial Old收集器。
ParNew 收集器
ParNew收集器是一个工作在新生代的垃圾收集器,其实说白了就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集之外,其他与Serial收集器相比并没有什么创新之处。
ParNew收集器也是独占式,在收集过程中,应用程序的线程会全部暂停,但是由于并行收集器使用多线程进行垃圾回收,在并发能力比较强的CPU上,它产生的停顿时间要短于Serial收集器的。
如果说ParNew收集器运行在多CPU的环境下,确实可以更快的完成垃圾收集。但是如果是在单个CPU的环境下,ParNew收集器是不如Serial高效的。
ParNew收集器只存在于新生代中。
Parallel 收集器
Parallel收集器除了和ParNew收集器一样是基于并行回收以外,Parallel也同样的使用了复制算法和独占式机制。
但是不同的是,Parallel收集器的关注点不同,它的关注点是系统的吞吐量,也被称作"吞吐量优先"的收集器。吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
需要注意的是,垃圾收集器中吞吐量和线程停顿时间是互相矛盾的,GC停顿时间缩短必须要牺牲吞吐量和新生代空间来换取,比如,系统把新生代调小一点,收集300M新生代肯定比收集500M新生代速度快,这也直接导致垃圾收集发生的更频繁,原来10秒收集一次,每次停顿100秒,现在变成5秒收集一次,每次停顿70秒,停顿时间下降了,但吞吐量也下降了。
同样的,老年代中也存在Parallel收集器,叫做Parallel Old收集器。
CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一款优秀的老年代垃圾收集器。
从名字可以看出,CMS为并发而生,并且使用的是标记清除算法。它的执行过程分为四个阶段:初始标记、并发标记、重新标记、并发清除。
初始标记是独占式,但它速度很快,仅仅只是标记GC Roots所能关联到的对象,并发标记会将之前的不可达的所有对象标记为垃圾,由于在并发标记阶段有可能有些标记过的对象引用发生变动,为了解决这个问题,CMS会进入到重新标记阶段,经历过这三个阶段后,CMS最终进入到并发清除阶段执行内存回收。
尽管CMS收集器采用的是并行回收,但是在初始标记阶段和重新标记阶段都使用的是独占式机制,需要暂停程序中的所有线程,可以说明目前所有的垃圾收集器都做不到完全不需要独占式机制,只是尽可能的缩短暂停时间而已。
注意:CMS使用的算法是标记清除,因此清除垃圾后,会残留大量空间碎片。为了解决这个问题,MCS收集器会默认清除垃圾后进行碎片整理,也因此停顿时间不得不增长。
G1 收集器
G1(Garbage-First)收集器是当今收集器技术发展最前沿的成果之一,它解决了前三个收集器的很多缺陷。
作为CMS的长期替代方案,G1使用了分代分区算法,特点如下:
1.并行性:G1在回收期间,可以由多个GC线程同时工作,有效利用多核计算能力。
2.并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此一般来说,不会再整个回收期间完全阻塞应用程序。
3.分代收集:G1和之前的回收器不同,它兼顾新生代和老年代,可以独立管理整个GC堆,能够采用不同的方式去管理新生代和老年代的对象。
4.空间整理:G1是基于标记压缩算法实现的收集器,在每次回收后都会有效的整理空间。
5.可预见性:由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收范围,对于应用线程停顿可以有很好的控制。
一切细节问题
System.gc()
在默认情况下,System.gc()会显示的触发Full GC,同时对老年代和新生代进行回收,一般情况下,垃圾回收应该是自动进行,不需要手动触发,如果过于频繁的触发垃圾回收对系统性能是没有好处的。
对象进入老年代
一般情况下,对象首次创建时,会被放置在新生代eden区,如果没有GC的介入,这些对象不会离开eden。
两种情况下对象会进入老年代,第一种是对象经历的GC次数达到一定的值,默认为15次,就会进入老年代。第二种情况是对象的体积过大,也非常有可能会直接进入老年代。