对象分类

JVM将对象分成不同的分代,然后根据每个分代的特点采取不同的垃圾回收算法。

  • 新生代:朝生夕灭的对象,比如方法的局部变量。
  • 老年代:存活的比较久,但是还是要死的对象。比如缓存对象,单例对象等。
  • 永久代:对象生成后几乎不灭的对象,例如加载过的类对象。

永久代是在方法区/元空间。新生代和老年代是在堆区。

新生代的垃圾回收

新生代采用的是复制算法,因为新生代对象的存活率比价低,因此可以不使用50%的内存作为空闲。一般的,使用两块10%的内存作为空闲和活动空间,而另外80%的内存,则是用来分配给新建的对象的。一旦发生GC,就将10%的活动区间与另外的80%中内存转移到10%的空闲区间中,接下来90%的内存会被释放,以此类推。

GHH8fg.png

HotSpot实现的复制算法流程

  1. 当Eden区满的时候,会触发第一次Minor gc,把还存活的对象复制到Survivor From区;当Eden区再次触发Minor gc的时候,会扫描Eden 区和Survivor From区,将存活的对象复制到Survivor To区,然后将Eden区和From区清空。
  2. 当后续Eden区又发生了Minor gc的时候,会堆Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden区和To区域清空。
  3. 部分对象会在From和To区域中复制来复制去,如此交换15次(可配置),最终如果还是存活,就存入到老年代。

一个对象的内存分配过程

GLtsG4.png

什么是空间分配担保机制

当发生minor gc的时候,JVM会首先检查老年代最大的可用连续空间是否运行大于最大新生代所有对象的总和,如果大于,那么这次YGC是安全的,如果不大于的话,JVM就需要判断HandlerpromotionFailure是否运行空间分配担保。

新生代采用的是复制算法,S0和S1始终只使用其中一块内存,当出现YGC后大部分的对象仍然存活的话,就需要老年代进行分配担保,把Survior区无法容纳的对象直接晋升到老年代。

那么这种空间分配担保的前提是老年代还有容纳的空间,一共有多少对象会活下来,再实际完成内存回收之前是无法明确知道的,所以只好取之前每次回收晋升到老年代对象容量的平均值最为经验值,与老年代的剩余空间做比较,决定是否进行Major GC来让老年代腾出更多的空间。

新生代收集器

Serial收集器

它是一个单线程收集器,并且在工作的时候,需要暂停所有的工作线程(STW),直到它收集结束。

优点:简单而高效,对于限定单个CPU的环境来说,Serial收集器由于没有线程切换的开销,效率较高。

缺点:需要停止用户线程。

适用环境:client模式下的默认新生代收集器。

ParNew收集器

它其实是Serial收集器的多线程版本,只能在新生代中使用。该收集器的并行度和CPU数量相同。

适用环境:Server模式下的默认新生代收集器。

Parallel Scavenge收集器

它也是一种并行的新生代垃圾收集器。采用的是复制算法。

它的特点是可以达成一个可控的吞吐量。

老年代

老年代一般采用的标记清除算法。标记清除算法可以分为两个步骤:

  • 标记过程:找到所有的可能访问的对象,做个指定的标记。
  • 清除过程:遍历堆内存,把未标记的对象进行回收。

老年代收集器

Serial Old收集器

它是一个单线程的收集器,采用的标记整理算法,。

Parallel Old收集器

Pareller Old收集器的老年代版本,使用多线程和“标记-整理”算法。

在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Paralllel Old收集器。

CMS收集器

CMS是老年代收集器,在收集过程中可以与用户线程并发操作。CMS牺牲了系统的吞吐量来追求收集速度,适合追求垃圾收集速度的服务器上。

CMS处理过程有七个步骤:

  • 初始标记,会导致STW
  • 并发标记,与用户线程同时运行
  • 预清理,与用户线程同时运行
  • 可被终止的预清理,与用户线程同时运行
  • 重新标记,会导致STW
  • 并发清除,与用户线程同时运行
  • 并发重置状态等待下次CMS的触发,与用户线程同时运行。