首页 🍄Java

Serial

Serial是一个新生代单线程收集器,在收集的时候会Stop The World!,也就是停止一切用户工作的线程。
新生代采用标记-复制算法来清理垃圾。
老年代(SerialOld)采用标记-整理来清理垃圾。
优点:

  • 简单高效(与其他收集器单线程相比)
  • 对于内存受限的环境,它是所有收集器中消耗额外内存最少的。
  • 在单核处理器或处理器核心数比较少的情况下Serial由于没有线程的开销,专心做垃圾清理可以获得单核最高效率。

适合用于客户端模式下的虚拟机。

缺点:

  • 单线程工作。
  • 垃圾收集时必须停止用户所有工作。

ParNew

ParNew可以看做是Serial的多线程并行版本,除了使用多线程收集外,其余行为与Serial完全一致。也是新生代收集器
ParNew在新生代采用标记-复制算法。

ParNew除了比Serial多了多线程并行收集外,并没有什么特别的创新的地方。
除了Serial只有ParNew能和CMS配合。

优点:

  • 加入了多线程并行收集垃圾。

缺点:

  • 在单核系统中ParNew绝不会有比Serial更好的效果。而且也会垃圾收集时停止用户线程。

Parallel Scavenge

Parallel Scavenge也是一个新生代收集器,同样基于标记-复制算法,多线程并行收集。
Parallel Scavenge对比ParNew的特别之处在于Parallel Scavenge注重于达到一个可控制的吞吐量。
吞吐量:用户代码运行时间比处理器运行总时间。

比如一个程序运行100分钟,用户使用99分钟,垃圾收集1分钟。吞吐量即为99%。
停顿时间越短越适合与用户的交互,保证服务响应质量的程序。
高吞吐量则可以高效利用处理器资源,尽快完成程序的计算任务,主要适合在后台运算而不太需要交互的分析任务。

Parallel 有一个 -XX:+UseAdaptiveSizePolicy 参数可以不需要人工指定新生代大小、Eden、Survivor区的比例,晋升老年代对象的大小等细节参数。虚拟机会根据当前性能情况,动态调整这些参数以提供合适的停顿时间或者最大吞吐量。这种调节方式被称为自适应的调节策略。

如果用户对收集器的运作不太了解手动优化存在困难使用Parallel配合自适应调节策略,把内存管理调度交给虚拟机是个不错的选择。

优点:

  • 可以控制吞吐量,高效利用处理器资源。
  • 有自适应调节策略可以自动调节内存。

缺点:

  • 收集时停顿。

Serial Old

Serial的老年代版本,同样是单线程,标记-整理算法收集。

这个收集器的主要意义也是供客户端模式下的HotSpot使用。

优缺点与Serial一样

Parallel Old

Parallel的老年代版本收集器,支持多线程并发收集,标记-整理算法收集。
可与Parallel Scavenge配合。

优缺点与Parallel一样

CMS

CMS收集器是一种以获取最短停顿时间为目标的收集器,目前很大一部分Java应用集中在互联网中或者浏览器的B/S系统服务端上,这类应用通常比较关注服务的响应速度,希望系统停顿时间尽可能短,可以给用户带来更好的体验,CMS就非常适合这类应用的需求。

CMS是根据标记-清除算法实现的,它的运作过程分为以下四个步骤:

  • 初始标记
  • 并发标记
  • 重新标记
  • 并发清除

其中初始标记和重新标记都是需要暂停用户操作的,初始标记就是标记能够GC Roots关联到的对象,速度很快。并发标记就是从GC Roots标记的对象开始遍历整个对象图,并发标记的过程比较漫长,用户可以进行操作。重新标记就是找出在并发标记时发生变动的对象,这一步时间稍长但也比并发标记短很多。并发清除就是清楚那些已经判断死亡的对象,同样用户可以操作。

优点:

  • 并发标记。
  • 低停顿时间。

不足:

  • 对资源敏感,在并发阶段由于可以与用户一起进行,所以会占用一部分系统资源,导致吞吐量降低。默认CMS启用的回收线程数为(处理器核心数量+3)/4,也就是说,如果处理器核心数在四个或以上,并发回收时垃圾收集线程只占用不超过25%的处理器运算资源,并且会随着处理器核心数量的增加而下降。但是当处理器核心数量不足四个时,CMS对用户程序的影响就可能变得很大。如果应用本来的处理器负载就很高,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然大幅降低。
  • CMS无法处理浮动垃圾,从而可会导致Full GC,在并发标记、清除阶段由于用户还是在运行的,这期间产生的垃圾是无法清理的,CMS只能把这些垃圾放到下次清理,这一部分垃圾被称为“浮动垃圾”。同样由于在垃圾收集阶段用户线程还要运行,所以还要预留一部分内存给用户。因此CMS无法像其他收集器那样可以等老年代几乎快满了再收集,必须留一部分空间给用户线程使用。在JDK5中CMS老年代被设置成68%会清理,在JDK6被提升到92%,但是这样就会出现风险:如果CMS运行期间预留的内存不够新创建的对象大小就不得不启动预备方案,冻结用户线程,启用Serial Old来重新进行老年代清理,这样冻结的时间可就长了。
  • 由于CMS是基于标记-清除算法实现,这个算法会产生很多空间碎片,在老年代中如果空间碎片过多会给创建大对象带来很多麻烦,,往往会出现老年代还有很多控件,但是无法创建对象,这个时候就不得不发生Full GC来清理老年代

G1

G1收集器是一款面向服务端应用的垃圾收集器,目前是JDK9的默认垃圾收集器。与其他收集器相比,G1具有如下特点:

  • 并行与并发。G1能充分利用多CPU,多核环境下的硬件优势。
  • 分代收集。能够采用不同的方式去处理新创建的对象和已经存活了一段时间的对象,不需要与其他收集器进行合作。
  • 空间整合。G1从整体上来看基于“标记-整理”算法实现的收集器,从局部上看是基于复制算法实现的,因此G1运行期间不会产生空间碎片。
  • 可预测的停顿。G1能建立可预测的时间停顿模型,能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

G1收集器将这个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但两者之间不是物理隔离的。他们都是一部分Region的集合。
0.png

每一个方块就是一个区域,每个区域可能是 Eden、Survivor、老年代,每种区域的数量也不一定。JVM 启动时会自动设置每个区域的大小(1M ~ 32M,必须是 2 的次幂),最多可以设置 2048 个区域(即支持的最大堆内存为 32M*2048 = 64G),假如设置 -Xmx8g -Xms8g,则每个区域大小为 8g/2048=4M。

G1收集器可以有计划地避免在整个Java堆全区域的垃圾收集。G1可以跟踪各个Region里面垃圾堆积的价值大小(回收所获得的空间大小及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,收集加载最大的region,这种方式保证了有限时间内可以获取尽可能多高的收集效率。

为了在 GC Roots Tracing 的时候避免扫描全堆,在每个 Region 中,都有一个 Remembered Set 来实时记录该区域内的引用类型数据与其他区域数据的引用关系(在前面的几款分代收集中,新生代、老年代中也有一个 Remembered Set 来实时记录与其他区域的引用关系),在标记时直接参考这些引用关系就可以知道这些对象是否应该被清除,而不用扫描全堆的数据。

G1收集器收集器收集过程有初始标记、并发标记、最终标记、筛选回收,和 CMS 收集器前几步的收集过程很相似。

  • 初始标记。标记出GC Roots直接关联的对象,这个阶段速度较快,需要停止用户线程,单线程执行。
  • 并发标记。从 GC Root 开始对堆中的对象进行可达新分析,找出存活对象,这个阶段耗时较长,但可以和用户线程并发执行。
  • 最终标记。修正在并发标记阶段引用户程序执行而产生变动的标记记录。
  • 筛选回收。选回收阶段会对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来指定回收计划(用最少的时间来回收包含垃圾最多的区域,这就是 Garbage First ,第一时间清理垃圾最多的区块),这里为了提高回收效率,并没有采用和用户线程并发执行的方式,而是停顿用户线程。

并发,并行

在收集器的语境中:
·并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线
程在协同工作,通常默认此时用户线程是处于等待状态。
·并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾
收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于
垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。




文章评论

目录