JAVA的内存回收机制(二)

May 11, 2016


  前面已经讲过了可达性分析的判定方式,但要在实际环境下完成可达性分析,不是只简单的扫一遍所有GC root就行的。为了保证可达性分析效果准确,进行这个可达性分析时,所有正在执行的线程都会被暂停,没错,是所有的。如果要完整的扫一遍整个内存,时间成本是难以令用户接受的,因为所有线程都暂停的时候程序就无响应了。实际操作的时候,JVM虚拟机会预先把对象的地址存起来,GC扫描的时候直接去这些特定的地址找对象,这样就能大大缩短GC扫描的时间。

  解决了GC扫描的问题还需要解决停线程的问题。因为线程不是你想停就能停的,在这个问题上JVM有安全点和安全区的概念。安全点就是线程能够暂停的点,可以把所有线程都停掉,再让未到安全点的线程重新开始,跑到安全点停下;也可以让线程主动轮询,发现需要GC的时候自己停在安全点。但是这样又有一个问题,如果线程处于挂起状态,既无法响应中断的命令也无法自己跑到安全点去。于是引入安全区的概念,安全区指引用关系不会变化的区域,线程一旦进入这个区域就立flag表示自己进入安全区,GC的时候收集器不会给这样的线程发中断请求。线程要离开安全区的时候,自己也会去判断是否完成了GC,以决定是继续执行还是等到GC完成。

  有了这一系列的机制保障,垃圾收集器就能够正确进行垃圾回收了,先来介绍当前主要的垃圾收集器,上一张经典的图片:

图片2

  上图很清晰地说明了当前主要的垃圾收集器以及他们所负责的区域,其中G1比较特殊,是可以回收整个堆的。连线表示它们可以形成配合,不连线就说明不能进行协同工作了。下面来逐个介绍这些收集器:

Serial-Serial Old

  这一对是最古老的收集器,都属于单线程收集器,Serial收集器采用复制算法,Serial Old收集器采用标记整理算法。这对搭配由于是单线程收集器,执行时需要停掉所有执行中线程。由于Serial收集器比较简单,对于Client模式下规模不大的应用还是比较合适的,停机的时间基本都在几十毫秒内。Serial Old收集器还可以与ParallelScavenge收集器配合使用,不过一般都是在服务端的程序。

ParNew

  这个收集器是Serial的进阶版,实现了多线程收集。这里的Parallel指的是并行,并行和并发的区别在于并行仅仅是多条垃圾回收线程一起工作。也就是说仍然还是要停掉所有工作线程的。这个收集器由于实现了并行,被广泛用于Server模式下的JAVA虚拟机。但是在客户端环境也就是单CPU或者CPU个数有限时并行效果甚至还不如Serial。由于和Serial有许多共同点,这个收集器和Serial一样可以和CMS收集器配合工作。

Parallel Scavenge

  同样是并行执行,同样是使用复制算法的新生代收集器,但加入了可控制吞吐量的参数。吞吐量的计算公式是 吞吐量=运行时间/(运行时间+GC时间),这个收集器作为优先控制吞吐量的收集器,还提供一个叫UseAdaptiveSizePolicy的参数,开启这个参数的时候虚拟机会启用自适应的调节策略,我们只需要设置吞吐量来给虚拟机一个优化的目标,虚拟机就会根据自适应的策略自己去调整,对于不熟悉JVM的用户很友好。

CMS

  CMS的C指的是concurrent,也就是并发,这是一款真正的并发收集器,能够实现在垃圾回收的同时工作线程也可以执行。这个收集器与Parallel Scavenge的区别在于它专注于获得做小的GC停顿时间,使用标记清除算法实现。这个收集器的收集过程分为初始标记、并发标记、重新标记和并发清除四个步骤,只有第一和第三个步骤需要停掉所有工作线程。第二个步骤和第四个步骤耗时较长,但可以与工作线程同时执行,这样就大大缩短了GC停顿时间。这个收集器启动的GC线程数和CPU个数有关,CPU个数多的时候占用资源比例较少,但是如果CPU少于4个,程序会因为并发GC线程占用过多资源变得很卡。所以CMS一般运行于Server环境,几十个CPU的机器上。

  CMS的一个局限是当并发清除阶段进行时程序会产生新的垃圾,这些新的垃圾只能在下次GC时再清理。而且CMS的并发收集程序需要一定的内存空间来运行,如果预留的内存不够,会临时启用Serial Old来收集老年代。

G1

  G1是在这些收集器中最新,也是最强大的收集器。能够实现并行与并发、独立管理整个GC堆,甚至还可以指定停机的时间。G1对堆的分区与其它的收集器都不一样,G1将内存分为很多个大小相等的独立区域,叫做Region,跟踪每个Region的回收价值,每次根据用户允许的回收时间回收价值最大的Region。对于在不同Region有关联的对象,G1都有对应的记录,保证GC扫描能够完全。G1的垃圾收集也分为四个步骤:初始标记、并发标记、最终标记、筛选回收。与CMS相似的是初始标记也是标记GC root能关联的对象,虽然需要停机但时间非常短。并发标记才是找出可达对象,最终标记同样是对并发标记的修正。筛选回收的耗时是根据用户规定来控制的,可控性很高。