深入理解Java虚拟机——垃圾收集器

1.前言

在前面我们已经说过了垃圾收集算法,那么现在我们要讲的垃圾收集器,实际上就是对垃圾收集算法的实践。

首先我们先看一张图,这张图可以帮助我们了解各款经典垃圾收集器之间的关系:

请添加图片描述

图中的垃圾收集器所在的区域代表了它是属于新生代或是老年代的收集器。

在正式了解垃圾收集器之前,我们务必要明确一个观点:

虽然垃圾收集器的技术在不断进步,但是直到现在也没有最好的垃圾收集器出现,更不存在万能的收集器,所以我们选择的只是对具体应用最合适的收集器。

2.垃圾收集器

2.1 Serial收集器

概要

Serial收集器是最基础、历史最悠久的收集器,这个收集器是一个单线程工作的收集器,单线程的意义并不是说它只会用一个处理器或者一条收集线程区完成垃圾收集工作,更重要的是强调它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。

运行过程

请添加图片描述

用法

虽然Serial是单线程进行垃圾收集,但是它也有自己的优势所在。迄今为止,它依然是HotSpot虚拟机运行在客户端模式下的默认新生代收集器,相较于其他收集器,他最大的优势就是简单而高效(与其他收集器的单线程相比)。

  • 对于资源受限的环境下,它是所有垃圾收集器里额外内存消耗最小的
  • 对于单核处理器或者处理器核心数较少的情况下,Serial收集器由于没有线程交互的开销,自然可以获得最高的单线程收集效率

在近年来流行的微服务应用中,分配虚拟机的内存一般不会特别大,收集几十兆甚至一两百兆的新生代,垃圾收集的停顿时间完全可以控制在十几、几十毫秒,只要不频繁发生垃圾收集,这点停顿时间对用户来说完全可以接受。利用这个优势,所以Serial收集器对于运行在客户端模式下的虚拟机来说是一个很好的选择

2.2 ParNew收集器

概要

ParNew收集器实质上是Serial收集器的多线程并发版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数(例如:-XX: SurvivorRatio-XX: PretrnureSizeThreshold-XX: HandlePromotionFailure等)、收集算法、Stop the World、对象分配规则、回收策略都与Serial收集器完全一致。

运行过程

请添加图片描述

用法

ParNew除了支持多线程以外,相对于Serial收集器并无太多创新,但是它却是不少运行在服务端模式下的HotSpot虚拟机。

很重要的原因是:除了Serial收集器外,只有它能与CMS收集器配合工作。

CMS作为老年代的收集器,无法与新生代中的Parallel Scavenge配合工作,所以在使用CMS收集器时,新生代只能选择Serial或者ParNew收集器。

正是CMS收集器出现巩固了ParNew的地位,JDK9直到G1出现,ParNew+CMS不再是官方推荐的服务器端的收集器解决方案了。从JDK9开始ParNew正式并入CMS,只能两者搭配适用。ParNew是HotSpot虚拟机中第一款退出历史舞台的垃圾收集器。

ParNew收集器在单核心处理器中绝对不会有比Serial更好的效果。

2.3 Parallel Scavenge收集器

概要

Parallel Scavenge收集器是一款新生代收集器,它同样基于标记-复制算法实现,也是能够并行收集的垃圾收集器。

Parallel Scavenge收集器的关注点与其他收集器不同,CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程等待的时间,而Parallel Scavenge的目标是达到一个可控制的吞吐量。

吞吐量 = 运行用户代码时间 / (运行用户时间代码 + 运行垃圾收集时间)

除此之外,Parallel Scavenge还提供了自适应调节策略!这也是该收集器的重要特点之一!

运行过程

请添加图片描述

用法

高吞吐量可以最高效率利用处理器资源,尽快完成程序的运算任务,主要适合后台运算不需要太多交互的分析任务

Parallel Scavenge提供了两个参数控制吞吐量:

  • 控制最大垃圾收集停顿时间:-XX: MaxGCpauseMillis
    • 改参数允许的值是一个大于0的毫秒数,收集器将尽力保证内存回收所花费的时间不超过用户设定值。
    • 注意:垃圾收集停顿时间缩短是牺牲吞吐量和新生代空间为代价换取的!不能简单的认为将改参数设置的小一点就可以使垃圾收集的速度变快。
  • 直接设置吞吐量大小:-XX: GCTimeRatio
    • 这个值应该设置为一个大于0小于100的毫秒数,也是垃圾收集时间占总时间的比率,相当于吞吐量的倒数。
    • 举个例子,我们将该参数设置为19,那么允许的最大垃圾收集时间占总时间就是5%(即1/(1+19) )。默认是99

除此之外,Parallel Scavenge还提供了一个参数用于自适应调节策略

  • 自适应调节策略:-XX: +UseAdaptiveSizePolicy
    • 当这个参数开启以后,就不需要指定新生代的大小、Eden和Survivor区的比例、晋升老年代对象的大小等细节参数,虚拟机会根据当前系统的运行情况收集性能监控信息动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量

如果我们对收集器的运作不太了解,手动优化存在困难的话,适用该自适应调节策略也是一个不错的选择。只需要设置最大堆内存,然后选择优化目标(停顿时间/吞吐量大小),具体的细节就有虚拟机自己来完成了。

2.4 Serial Old收集器

概述

Serial Old是Serial收集器的老年代版本。它同样是一个单线程收集器,适用标记-整理算法。

运行过程

参考Serial收集器中的图。

用法

该收集器主要的意义也是供客户端模式下的HotSpot虚拟机上使用

如果用在服务端模式下,可能有两种用途:

  • 在JDK5版本之前搭配Parallel Scavenge收集器使用
  • 作为CMS收集器失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用

2.5 Parallel Old收集器

概述

Parallel Old是Parallel Scavenge收集器的老年代版本,同样支持多线程并发收集,基于标记-整理算法实现。

运行过程

同样见Parallel Scavenge收集器中的图。

用法

注重吞吐量或者处理器资源比较稀缺的情况下,可以优先考虑Parallel Scavenge + Parallel Old收集器这个组合。

2.6 CMS收集器

概要

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。其基于标记-清除算法实现。

运行过程

请添加图片描述

CMS垃圾收集一共可以分为四个步骤:

  • 初始标记:仅仅标记一下GC Roots能够直接关联到的对象,速度很快
  • 并发标记:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但是不需要停顿用户线程,可以同垃圾收集线程一起并发运行
  • 重新标记:修正并发期间,因用户程序继续运作导致标记产生变动的那一部分对象标记记录,这个阶段的停顿时长比初始标记要长,但是远比并发标记的时间要短
  • 并发清除:清理删除掉标记阶段判断已经死亡的对象,由于不需要移动存活对象,所以该阶段也可以与用户线程并发运行

以上四个步骤,初始标记与重新标记仍然需要Stop The World

用法

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

缺点

CMS是一款优秀的垃圾收集器,是HotSpot虚拟机追求低停顿第一次成功尝试。

不过他还远达不到完美的地步,CMS至少有以下三个明显的缺点

  • CMS收集器对处理器资源非常敏感
    • 在并发阶段,虽然不会导致用户线程停顿,但是由于占用了一部分CPU资源,会导致应用程序变慢,降低吞吐量。
    • CMS默认启动的回收线程是(处理器核心+3)/4个,也就是说如果处理器核心在4个或以上,并发回收时垃圾处理线程只占用不到25%的处理器资源,并且会随着处理器核心的增加而下降。但是当处理器核心不足4个的时候,CMS对用户的影响就可能变得很大。
  • CMS收集器无法处理浮动垃圾,有可能出现Con-current Mode Failure失败进而导致另一次完全Stop The World的Full GC的产生。
    • 浮动垃圾:指在CMS并发标记和并发清理阶段,用户线程还是在继续运行的,程序在运行自然还会伴随新的垃圾对象不断产生,但是这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理它们,只好留待下一次收集时再清理掉。这一部分垃圾就叫做浮动垃圾
    • 由于在垃圾收集阶段用户线程还需要持续运行,那就还需要预留足够内存空间提供给用户线程使用,因此CMS收集器不能像其他收集器那样等待 到老年代几乎完全被填满了再进行收集,必须预留一部分空间供并发收集时的程序运作使用
      • 在JDK 5的默认设置下,CM S收集器当老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果 在实际应用中老年代增长并不是太快,可以适当调高参数-XX:CMSInitiatingOccu-pancyFraction的值来提高CMS的触发百分比,降低内存回收频率,获取更好的性能。
      • 到了JDK 6时,CMS收集器的启动阈值就已经默认提升至92%。但这又会更容易面临另一种风险:要是CMS运行期间预留的内存无法满足程序分配新对象的需要就会出现一次并发失败(Concurrent Mode Failure),这时候虚拟机将不得不启动后备预案:冻结用户线程的执行,临时启用Serial Old收集器来重新进行老年代的垃圾收集, 但这样停顿时间就很长了。
  • 由于CMS收集器基于标记清除算法实现,意味着收集结束时会有大量的空间碎片产生

2.7 Garbage First收集器

概要

Garbage First(简称G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局模式。

G1是一款“停顿时间模型”的垃圾收集器。

所谓停顿时间模型的意思就是能够支持指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间大概率不会超过N毫秒这样的目标。

G1之所以能够够做到停顿时间模型主要有两个原因:

  • G1开创的基于Region的堆内存布局是他能够实现这个目标的关键。

    • G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。

    • Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为 1MB~32MB,且应为2的N次幂。而对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待,如下图(G1收集器Region分区示意图)所示:

    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rzvY16b6-1683418863157)(深入理解Java虚拟机——垃圾收集器.assets/d5a2385dd9eb725b93c5d4edf37b7fc6.png)]

    • 虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域(不需要连续)的动态集合。

  • G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。

    • 更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒),优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。

这种使用Region划分内存空间以及具有优先级的区域回收方式保证了G1收集器在有限的时间内获取尽可能高的收集效率

运行过程

请添加图片描述

G1收集器的运作过程大致可划分为以下四个步骤:

  • 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
  • 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
  • 最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
  • 筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。

G1收集器除了并发标记外其余阶段都是需要暂停用户线程的。换言之,它并非纯粹地追求低延迟,官方给它设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才能担当起“全功能收集器”的重任与期望。

用法

JDK 8 Update 40 版本之后,G1收集器被Oracle官方称为“全功能的垃圾收集器”(Fully-Featured Garbage Collector)。

G1是一款主要面向服务端应用的垃圾收集器。JDK 9之后,G1取代了Parallel Scavenge加Parallel Old组合,成为服务端模式下的默认垃圾收集器。

3.拓展知识

关于G1与CMS的经验之谈

目前在小内存应用上CMS的表现大概率要会优于G1,而在大内存应用上G1则大多能发挥其 优势,这个优劣势的Java堆容量平衡点通常在6GB至8GB之间。不同应用需要量体裁衣地实际测试才能得出最合适的结论,随着HotSpot的开发者对G1的不断优化,也 会让对比结果继续向G1倾斜。

本文参考自《深入理解Java虚拟机》(第三版)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/17458.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【三十天精通Vue 3】第二十六天 Vue3 与 TypeScript 最佳实践

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: 三十天精通 Vue 3 文章目录 引言一、为什么使用TypeScript?二、Vue 3和TypeScript的基础2.1 安装TypeScript2.2 配置TypeScript2.3 Vue 3中使用TypeScript

Java多线程基础概述

简述多线程: 是指从软件或者硬件上实现多个线程并发执行的技术。 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。 正式着手代码前,需要先理清4个概念:并发,并行,进程&#…

TinyJAMBU的制动原理——一种轻量化的认证密码

关于TinyJAMBU的定义和介绍在另一篇博文已经介绍过了,这里只对其动作原理进行描述和说明。 对应的博文链接如下:TinyJAMBU:一种轻量化密码介绍 首先,该密码是一个流密码体系的块密码框架。其加密模式整体上来看是块密码&#xff0…

让语言学习更简单的 WordFlow

作为一个英语并不是那么特别好的计算机专业学生,长期积累英语的学习对个人发展还是有意义的。简单来说,我在语言上最大的两个问题,一个自己「不理解」,另一个是自己「不会表达」。 上述两个问题主要体现在口语层面,而…

实验二 存储器管理

实验二 存储器管理 实验目的: 理解各类置换算法的原理和虚拟存储器管理的方法。 实验内容: 编程实现LRU算法或CLOCK/改进算法等置换算法(二选一),模拟实现虚拟存储器的地址变换过程。 实验步骤: 1…

【Golang项目实战】用Go写一个学生信息管理系统,真的太酷啦| 保姆级详解,附源码——建议收藏

博主简介:努力学习的大一在校计算机专业学生,热爱学习和创作。目前在学习和分享:数据结构、Go,Java等相关知识。博主主页: 是瑶瑶子啦所属专栏: Go语言核心编程近期目标:写好专栏的每一篇文章 学习了Go的基…

图神经网络:在自定义数据集上动手实现图神经网络

文章说明: 1)参考资料:PYG官方文档。超链。 2)博主水平不高,如有错误还望批评指正。 文章目录 自定义数据集动手实现图神经网络自定义数据集训验测集拆分,创建Data的数据结构,观察Data的基本信息,可视化图网…

震惊,为了学会泛型类竟做这种事?!

上一节,我们基本学会了Java泛型类的用法。 传送门:彻底弄懂Java的泛型 - 泛型类 这一节,我们转变一下风格,具体是什么风格呢,你马上就懂了。 宝子们,欢迎大家来到我们的泛型直播间,这一讲呢&a…

Ansible的脚本-playbook 剧本

目录 1.剧本(playbook) 1.playbook介绍 2. playbooks 的组成 3.案例:编写httpd的playbook 4.定义、引用变量 5.指定远程主机sudo切换用户 6.when条件判断 7.迭代 2.playbook的模块 1.Templates 模块 2.tags 模块 3.Roles 模块 1.…

【Linux从入门到精通】vim的基本使用各种操作详解

文章目录 一、vim编辑器简单介绍 二、vim编辑器的四种模式 2、1 正常/普通/命令模式(Normal mode) 2、2 插入模式(Insert mode) 2、3 末行模式(last line mode) 三、命令模式的相关操作实例 3、1 光标的相关操作 3、2 文本操作 四、插入模式下的相关操作 五、末行模式下的相关操…

Java—JDK8新特性—函数式接口

目录 函数式接口 3.1 什么是函数式接口 3.2 functionalinterface注解 源码分析 3.3 Lambda表达式和函数式接口关系 3.4 使用函数式接口 函数式接口 3.1 什么是函数式接口 如果一个接口中只包含一个抽象方法,这个接口称为函数式接口 如果一个接口包含&#xff0…

K8S管理系统项目实战[API开发]-2

后端: gogin 后端代码地址GitHub - yunixiangfeng/k8s-platform: K8s管理系统后端: gogin 5、存储与配置 5.1 ConfigMap 5.2 Secret 5.3 PersistentVolumeClaims 6、工作流 6.1 流程设计 6.2 数据库操作(GORM) (1)初始化…

交换机-Exchanges

交换机 Exchanges 概念 RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。实际上,通常生产者甚至都不知道这些消息传递传递到了哪些队列中。相反,生产者只能将消息发送到交换机(exchange),交换机工作的内容非常简…

正则表达式-基本元字符和语法规则

© Ptw-cwl 文章目录 字符匹配元字符.元字符[]元字符[^]元字符*元字符元字符?元字符{}元字符|元字符()元字符^元字符$元字符\元字符\d元字符\w元字符\s元字符\b元字符\B元字符*?、?、??、{n,m}?元字符(?)、(?!)元字符(?:)元字符\1、\2等元字符^、$元字符&#x…

JavaSE基础(二)—— 类型转换、运算符、键盘录入

目录 一、类型转换 1. 自动类型转换 1.1 自动类型转换的底层原理: ​1.2 自动类型转换的其他形式​编辑 2. 表达式的自动类型转换 3. 强制类型转换 3.1 强制类型转换底层原理​编辑 3.2 注意事项 二、运算符 1. 算数运算符 1.1 案例:数值拆分…

PCA主成成分分析例题详解

主成分分析是一种降维算法,它能将多个指标转换为少数几个主成分,这些主成分是原始变量的线性组合,且彼此之间互不相关,其能反映出原始数据的大部分信息 需要了解具体细节可看此视频👉:什么是主成成分分析PC…

Linux安装MongoDB数据库,并内网穿透远程连接

文章目录 前言1. 配置Mongodb源2. 安装MongoDB3. 局域网连接测试4. 安装cpolar内网穿透5. 配置公网访问地址6. 公网远程连接7. 固定连接公网地址8. 使用固定地址连接 转载自Cpolar Lisa文章:Linux服务器安装部署MongoDB数据库 - 无公网IP远程连接「内网穿透」 前言 …

SpringBoot访问静态资源

SpringBoot项目中没有WebApp目录,只有src目录。在src/main/resources下面有static和templates两个文件夹。SpringBoot默认在static目录中存放静态资源,而templates中放动态页面。 static目录 SpringBoot通过/resources/static目录访问静态资源&#xff…

完成A轮融资,倍思如何发力场景化为品牌创造广阔未来?

凛冬过后的消费电子正在重新凝聚资本的目光。 近日,深圳市倍思科技有限公司宣布完成由深创投、中金资本联合领投,越秀产业基金、高榕资本跟投,金额数亿元人民币的A轮融资。 分析人士指出,消费电子的行业景气度在逐渐恢复&#x…

中国社科院与美国杜兰大学金融管理硕士项目——迎接立夏,切莫忘记自我成长

五月的风吹走了春季,今天我们迎来立夏。作为夏季的第一个节气,立夏常被人们当做万物蓄满能量,即将加速生长的标志。而在职的我们,也应该跟这世间万物一样,在季节交替之时沉淀自己、努力向上成长。在社科院与杜兰大学金…