JAVA程序员养成计划之JVM学习笔记(2)-垃圾收集管理

本文对JVM垃圾收集进行说明,包括三种不同算法(标记复制、标记清除、标记整理),以及七种不同的垃圾收集器(Serial,ParNew,Serial Scavenge, CMS, Serial Old, Parallel Old, G1)
持续更新中… …

1. 垃圾回收相关概念

1.1. 垃圾回收对象

说到垃圾收集,首先得确定哪些是可回收的对象,这里涉及到java的四种引用方式,即强、软、弱、虚四类引用。

  • 强引用:即普遍存在的对对象的引用,如指向通过new创建的对象
  • 软引用:即程序运行非必须的对象,仅在程序运行过程中快要发生内存溢出的时候,会对这部分进行垃圾回收
  • 弱引用:弱引用也是非必需的对象,不过与软引用的区别是一旦垃圾回收器发现弱引用的存在,就会将之回收掉。
  • 虚引用:不能通过虚引用获取一个对象实例,虚引用的存在基本只是为了在这个对象被垃圾回收之前触发一次通知事件。

1.2. 拉圾回收区域

前文中已经对运行时数据区域进行了简单的说明,这里要说的是垃圾收集主要涉及的区域,也就是堆区。堆区之所以还要细分为新生代和老年代,是为了垃圾回收的方便,把一些不经常回收或者体积比较大,转移起来比较麻烦的对象放在老年代,把一些经常会被回收的对象放到新生代,然后对不同的区域采用不同的垃圾回收器,更能提高垃圾回收的效率。

1.3. 垃圾回收对象判定

通常情况下,判断一个对象实例是否应该被回收主要使用的是可达性分析算法,即通过GC Roots的引用关系进行遍历,能否到达该对象实例。其中,能够作为GC Roots的对象主要由以下:

  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 虚拟机栈中局部变量表引用的对象
  • 本地方法栈中JNI(Java Native Interface)引用的对象

2. 垃圾收集算法

垃圾回收算法主要分为三种,即标记清除、标记复制、标记整理。

  • 【标记清除(Mark-Sweep)】:正如它的名字一样,标记清除算法就是先对所有需要回收的对象进行标记,然后把这部分给清除掉。这是一个很简单的算法,不过有两个缺点,一个是两个步骤的效率都不太高,另一个则是很明显的会产生大量的内存碎片。标记清除算法的示意图如下:
    JAVA程序员养成计划之JVM学习笔记(2)-垃圾收集管理_第1张图片
  • 【标记复制(Mark-Copying)】:标记复制主要是考虑到标记清除算法中会产生大量内存碎片的算法提出来的,具体实现为将内存区域划分为两块,每次都会有一块不会被使用,算法运行过程就是将另一块在使用的内存区中的还存活的对象依次放入那块没有被使用的内存块中,然后对原来那块进行一次性清除。这样的好处在于不会产生内存碎片,而且实现也简单高效。缺点就是因为有一块内存不能使用,内存的利用率就不能达到全部。标记复制算法的示意图如下:
    JAVA程序员养成计划之JVM学习笔记(2)-垃圾收集管理_第2张图片
  • 【标记整理(Mark-Compact)】:标记整理考虑到的是内存碎片还有内存利用率两方面的因素,也就是标记之后,对存活的对象进行移动,使得存活的对象都在内存一端,另一端则是空闲内存。标记整理的示意图如下:
    JAVA程序员养成计划之JVM学习笔记(2)-垃圾收集管理_第3张图片

3. 垃圾收集器

垃圾收集器目前来看主要有7种(Serial,ParNew,Serial Scavenge, CMS, Serial Old, Parallel Old, G1),从新老生代的划分来看,如图所示:
JAVA程序员养成计划之JVM学习笔记(2)-垃圾收集管理_第4张图片
位于上方Young Generation部分的为新生代收集器,位于下方Tenured Generation部分的为老年代收集器,其中连线关联的收集器代表这两个可以一起协同使用。

3.1. Serial

串行收集器,需要停止所有其他线程开始收集垃圾,对新生代的对象采用标记复制的收集方式,即将Eden区和From Survivor区的存活对象复制到To Survivor区,如果满了就朝来年代复制。

3.2. Serial Old

串行收集器,和Serial类似,不过是对老年代进行收集,对老年代的收集方式为标记整理。

3.3. ParNew

新生代的并行收集器,采用标记复制算法,实际上是Serial的多线程版本,在垃圾回收阶段启用多线程进行收集。

3.4. Parallel Scavenge

新生代的并行收集器,和ParNew类似,也采用了标记复制算法,不过它的关注点在于使CPU运行用户代码时间和总的消耗时间的比值也就是吞吐量达到一个可控的量,既可以控制吞吐量为百分之几,然后让jvm自行确定各类GC以及VM参数

3.5. CMS

CMS全称为Concurrent Mark Sweep收集器,专注于最短停顿时间,即给用户最好的体验。这个收集器总体而言是采用的标记清除算法,总共分为四个步骤:

  • 初始标记:与用户线程不能并行,不过这一阶段仅仅标记GC Roots能关联的对象
  • 并发标记:这一阶段能够和用户线程并发执行,即顺着GC Roots标记做可达性监测
  • 重新标记:这一阶段主要是标记那些在并发标记阶段,由于用户线程继续运行而导致的部分标记发生变化的那一部分,这一阶段也不能和用户线程并发执行。
  • 并发清除:这一阶段与用户线程一同运行,这里采用的是清除策略

从CMS的步骤可以看出,它优点就是减少用户的停顿时间,能够达到好的用户体验度,缺点就是无法处理浮动垃圾,浮动垃圾指在并发清除阶段因为用户线程还在运行所产生的垃圾。因为垃圾收集与用户线程并发执行,所以需要预留一些空间给用户线程使用,不能像其他收集器一样等到内存快满的时候才开始使用,预留的大小通过配置实现,如果预留的空间太小,会造成concurrent mode failure错误,这个时候就会启用serial old收集器重新进行垃圾收集。
除此之外,CMS还有个缺点就是采用的是垃圾清除算法,所以会产生不少内存碎片,可以通过设置让内存整理过程进行到一定次数后进行一次碎片整合,整合过程是不能与用户线程并发的,所以阈值设定也需要根据情况设置。

3.6. Parallel Old

即采用并行线程对老年代进行垃圾回收,采用的标记整理算法。

3.7. G1(Garbage First)

G1收集器与其他收集器有个显著的不同就是,垃圾回收不再是整个新生代或者老年代,它将Java堆内存布局划分成了多个大小相等的Region。G1会跟踪每个Region的垃圾价值,即对这个Region进行回收所获得的空间大小和时间的大小的比值,然后维护一个优先队列,每次根据允许的收集时间,回收一个价值最大的Region。
为了避免各个Region之间互相引用所造成的垃圾回收方面的麻烦,G1的每个Region会有一个Remembered Set,用于存储其他Region对这个Region的对象的引用。内存回收时,枚举GC Roots时也将这一部分加入即可。
G1收集器的步骤如下:
- 初始标记:标记GC Roots关联对象,修改Next Top Mark Start的值,使得用户线程在后续并发执行时能在正确的Region中创建对象。
- 并发标记:和CMS类似,从GC Roots开始进行可达性分析。
- 最终标记:用户线程并发执行过程中会生成Remembered Set Logs,记录对象的变化记录,在最终标记阶段需要把这个记录合并到Remembered Set中。
- 筛选回收:对各个Region的回收价值与成本进行评估,按照用户所希望停顿的GC时间制定回收计划。

4. 参考资料

[1]《深入理解Java虚拟机》第二版,周志明著
[2]GC其他:引用标记-清除、复制、标记-整理的说明

你可能感兴趣的:(java,jvm,cms,垃圾收集器,垃圾收集算法)