《深入理解Java虚拟机》—— 堆的划分与垃圾回收

堆的划分与垃圾回收

堆的划分

可以把Java堆分为新生代(Young Generation)老年代(Old Generation),又将新生代分为Eden(伊甸)区,From Survivor区,To Survivor区,它们默认的内存分配比例为8:1:1.
《深入理解Java虚拟机》—— 堆的划分与垃圾回收
勘误:Old Gen(约占1/3堆空间) 更正为 Old Gen(约占2/3堆空间)

Minor GC, Major GC,Full GC的区别

GC分为3种:Minor GC, Major GC,Full GC

  • Minor GC发生在新生代中,采用复制算法;
  • Major GC发生在老年代中,采用标记—清除算法或标记—整理算法;
  • Full GC包括一次Minor GC和一次Major GC。

Java对象具有“朝生夕灭”的特点(即生命周期很短),所以Minor GC十分频繁,速度快;而Major GC比Minor GC速度一般慢十倍以上。

对象的分配

新建的对象一般被分配到EdenFrom区(需要较大连续空间的对象直接被移入老年代),经过一次Minor GC后,Eden和From中存活的对象被移入To区,然后这个To区成为下一次Minor GC的From区,继续扫描(由此可见,From区和To区时不断交替互相成为的,且这两区在任一时刻必有一区为空,所以每次新生代中可用的内存空间为整个新生代空间的90%(80%+10%))。以后对象每熬过一次Minor GC,对象的年龄 +1 岁,当年龄到某个值时(默认15岁),对象晋升到老年代。

注:当移入To区时如果To区空间不够用,就会将To区无法容纳的对象放入老年代,这一机制成为“分配担保”。

判断对象的是否需要回收

首先判断对象是否可用(有两种算法):引用计数算法,可达性分析算法。

  • 引用计数算法:给对象添加一个引用计数器,被引用一次则+1,当引用失效就-1,当计数器为0时该对象就是不可再被引用。(这种算法实现简单效率也高,但很难处理对象间相互循环引用的情况)。
  • 可达性分析算法:在Java中,通过可达性分析算法来判断对象是否存活
    通过一系列称为 GC Roots 的对象作为起点,向下搜索,走过的路径称为引用链 (Reference Chain),当有对象与GC Roots不可达时,该对象不可再被引用。
    下图绿色对象为存货对象,红色对象为不可用对象。
    《深入理解Java虚拟机》—— 堆的划分与垃圾回收
    注:当对象与GC Roots不可达时,也不意味着对象已经死亡了,真正宣布对象死亡还需要两次标记过程:1)第一次标记并筛选,筛选的条件为该对象是否有必要执行 finalize() 方法,若对象没有覆盖 finalize() 方法,或该方法已经被JVM调用过的,则认为没有必要执行 finalize() 方法,回收该对象;2)若该对象有必要执行 finalize() 方法,则该对象会被放入一个叫 F-Queue的队列中,并由JVM创建的 Finalizer 线程执行 finalize() 方法进行回收。

垃圾回收算法

垃圾回收算法分为三种:复制算法,标记—清除算法,标记—整理算法

  • 复制算法
    将可用内存划分为两块,每次只是用一半,当这一半的内存用完了,就把存活的对象复制到另一半上,然后把已使用的内存清理(由此也可验证From区与To区必有一区为空,因为一旦复制到To区,From区的内存就被清理了)。

  • 标记—清除算法
    标记出需要回收的对象,然后回收所有被标记的对象。这种方法会产生大量不连续的内存碎片。

  • 标记—整理算法
    标记—清除算法的改进版,在标记完后,先让存活的对象向一端移动,然后清理掉端边界以外的内存。这样就不会造成许多不连续的内存碎片了。

HotSpot采用分代收集算法,即新生代采用复制算法,老年代采用标记—整理算法。

垃圾收集器

垃圾收集器共分为7种:Serial收集器,ParNew收集器,Parallel Scavenge收集器,CMS收集器,Serial Old收集器,Parallel Old收集器,G1收集器。下图是它们搭配使用关系,有连线说明它们可以相互搭配使用。
《深入理解Java虚拟机》—— 堆的划分与垃圾回收

Serial收集器

仅使用一个线程完成垃圾回收,在垃圾回收时暂停其他所有线程(称为Stop The World),直到回收结束。
《深入理解Java虚拟机》—— 堆的划分与垃圾回收

ParNew收集器

Serial收集器的并发(多线程)版本。
《深入理解Java虚拟机》—— 堆的划分与垃圾回收

Parallel Scavenge收集器

与ParNew类似,不过Parallel Scavenge收集器关注的是CPU的吞吐量,而其他收集器关注的是如何缩短垃圾回收时用户线程的停顿时间(也就是缩短Stop The World的时间)。吞吐量 = 运行代码时间 / (运行代码时间 + 垃圾回收时间)。
《深入理解Java虚拟机》—— 堆的划分与垃圾回收

CMS收集器(Concurrent Mark Sweep)

使用标记—清除算法,整个过程分为4步:初始标记,并发标记,重新标记,并发清除。(其中初始标记和重新标记会触发Stop The World)。

有3个缺点:1)对CPU资源非常敏感;2)无法处理“浮动垃圾”,可能会出现Concurrent Mode Failure(在并发清除时用户线程也在运行,自然就会同时产生新的垃圾,这些垃圾只能留在下次清理,称为“浮动垃圾”),所以并发清楚时需要预留一定内存空间(给“浮动垃圾”),若预留的空间无法满足程序需要,则发生Concurrent Mode Failure,启动后备预案,临时启用Serial Old收集器,导致有一次Major GC);3)产生大量的内存碎片,这是标记—清除算法导致的。

Serial Old收集器

Serial收集器的老年代版本,除此之外还可作为CMS收集器发生Concurrent Mode Failure时的后备预案。

Parallel Old收集器

Parallel Scavenge收集器的老年代版本。
《深入理解Java虚拟机》—— 堆的划分与垃圾回收

G1收集器(Garbage-First)

整个过程分为4步:初始标记,并发标记,最终标记,筛选回收。
有以下4个特点:

  • 并行与并发:可以并行执行以减少Stop The World的停顿时间,也可以并发的执行垃圾回收线程与用户线程。
  • 分代收集:虽然保留了分代(新生代与老年代)概念,但G1将堆分为多个大小相等的独立区域(Region)。
  • 空间整合:从整体上看是使用的标记—整理算法,实际上是两个Region之间的“复制”算法。
  • 可预测的停顿:G1可以建立可预测的停顿时间模型是因为G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护了一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,这也是G1名字中First(价值优先)的含义。