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

news2024/9/25 7:22:28

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虚拟机》(第三版)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学系统集成项目管理工程师(中项)系列18b_进度管理(下)

1. 制定进度计划 1.1. 分析活动顺序、持续时间、资源需求和进度制约因素,创建项目进度模型的过程 1.2. 输入 1.2.1. 进度管理计划 1.2.2. 活动清单 1.2.3. 活动属性 1.2.4. 项目进度网络图 1.2.5. 活动资源需求 1.2.6. 资源日历 1.2.7. 活动持续时间估算 …

Linux网络编程——套接字网络编程预备知识

0.关注博主有更多知识 操作系统入门知识合集 目录 1.理解IP地址和MAC地址 2.认识端口号 3.感性认识TCP协议和UDP协议 4.网络字节序 5.socket编程接口 1.理解IP地址和MAC地址 事实上在上一篇博客当中粗浅了介绍一个IP地址MAC地址,其中我们知道IP地址提供方向…

坐标系变换推导(欧拉角、方向余弦矩阵、四元数)+代码解析

一、为什么选择四元数 描述两个坐标系之间的变换关系主要有几个方法 1、欧拉角法(存在奇异性和万向锁而且三个轴旋转的顺序不好定) 2、方向余弦矩阵法(翻译为Directional cosine matrix,简称DCM,也称为旋转矩阵,看了很多博客写的是C11-C33的那…

【Python实操】如何快速写一个乒乓球游戏?

文章目录 前言一、 导入 turtle 和 Screen二、创建一个球3.创建一个 AI 挡板4.创建自己的挡板5.创建移动AI挡板的函数6.创建一个函数以移动你的挡板并用键盘控制它7.全部代码总结 前言 本文提供了一个 Python 实现的乒乓球游戏代码,你只需要将代码复制并粘贴到编辑…

如何防御流量攻击

随着互联网的发展,网络安全问题也日益突出。其中,流量攻击成为网络攻击的一种常见手段。那么流量攻击属于什么攻击,服务器防御流量攻击的方法有哪些呢?本文小编将为您一一解答。 一、流量攻击是什么? 流量攻击即DDoS攻击,全称为…

如何进行DNS优化

在互联网时代,网站的访问速度直接影响着用户体验和转化率。而DNS(Domain Name System)作为域名解析系统,负责将域名转换为IP地址,是网站访问速度的重要因素之一。因此,DNS优化成为了提升网站速度的重要手段之一。 DNS优化到底是什…

SpringMVC-RESTful

REST风格 1. REST简介1.1 REST介绍1.2 RESTful介绍1.3 注意事项 2. RESTful入门案例2.1 快速入门2.2 PathVariable介绍2.3 RequestBody、RequestParam、PathVariable区别和应用 3. REST快速开发【重点】3.1 代码中的问题3.2 Rest快速开发 4. 案例:基于RESTful页面数…

拿捏c语言循环

📕博主介绍:目前大一正在学习c语言,数据结构,计算机网络。 c语言学习,是为了更好的学习其他的编程语言,C语言是母体语言,是人机交互接近底层的桥梁。 本章用循环去写一些题目。 让我们开启c语言…

Kubernetes系列---Kubernetes 理论知识 | 初识

Kubernetes系列---Kubernetes 理论知识 | 初识 1.K8s 是什么?2.K8s 特性3.小拓展(业务升级)4.K8s 集群架构与组件①架构拓扑图:②Master 组件③Node 组件 五 K8s 核心概念六 官方提供的三种部署方式 1.K8s 是什么? K…

springboot打包成jar包运行到服务器 java v 1.8

1.项目打包成jar包 1.1 1.2 2 jdk安装 2.1 jdk 官网 -> oracle 官方的jdk https://www.oracle.com/java/technologies/downloads/#java8 2.2 本地上传文件到服务器 2.3 配置安装 tar -zvxf jdk-8u131-linux-x64.tar.gz -->解压修改配置文件 source /etc/profile /…

Java 10 字符串

1.API 1.1API 概述 什么是API ​ API (Application Programming Interface) :应用程序编程接口 java 中的 API ​ 指的就是 JDK 中提供的各种功能的 Java 类,这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只…

C++好难(3):类和对象(中篇)

【本章目标】 类的6个默认成员函数构造函数析构函数拷贝构造函数赋值运算符重载const成员函数取地址及const取地址操作符重载 目录 【本章目标】 1.类的6个默认成员函数 2.构造函数 2.1概念 2.2构造函数的特性 特性一 特性二 特性三 特性四 特性五 特性六 特性七 …

Monkey Patching in Go

gomonkey 用来给函数打桩,这种使用一个新的方法实现来替换原来的实现逻辑,怎么看都觉得很神奇。举个例子,在单测中方法 json.Marshal 可以被 gomonkey 覆写成另一种逻辑实现,我准备从原理和使用的角度来看看 gomonkey。主要是来看…

LeetCode 第 344 场周赛

相当的惨烈&#xff0c;乱交 Q1 前后缀分解,用set统计不同元素的个数 class Solution {public:vector<int> distinctDifferenceArray(vector<int>& nums) {int n nums.size();vector<int> L(n 1, 0), R(n 1, 0); // 前缀不同数的个数set<int&g…

MLC LLM - 大模型本地部署解决方案

MLC LLM 是一种通用解决方案&#xff0c;它允许将任何语言模型本地部署在各种硬件后端和本地应用程序上&#xff0c;此外还提供了一个高效的框架&#xff0c;供每个人根据自己的用例进一步优化模型性能。 推荐&#xff1a;用 NSDT设计器 快速搭建可编程3D场景。 我们的使命是让…

【Python】使用Print函数制作旋转的动画

1. 引言 如果你想有效地学习Python&#xff0c;这篇文章可能不适合你。接下来的一切都可能是愚蠢、和浪费时间&#xff0c;但哪有怎么样&#xff0c;毕竟这玩意很有趣呀&#xff01; 2. 好玩的脚本 首先&#xff0c;我们来看两个好玩的Python脚本&#xff0c;如下&#xff1…

开关电源基础03:正激和反激开关电源拓扑(3)-反激拓扑

说在开头&#xff1a;关于不确定性原理 1927年2月&#xff0c;那个冬天对海森堡来说简直是一场噩梦&#xff0c;越来越多的人转向了薛定谔和他那该死的波动理论&#xff0c;把他的矩阵忘得一干二净&#xff1b;而最让他伤心和委屈的是&#xff0c;玻尔也转向了他的对立面&…

大规模并行处理架构Doris编译部署篇

目录 1 Doris编译1.1 使用 Docker 开发镜像编译&#xff08;推荐&#xff09;1.1.1 遇到的问题1.1.1 遇到的问题 1.2 直接编译&#xff08;CentOS/Ubuntu&#xff09;1.2.1 环境准备1.2.2 系统依赖&#xff08;一次性安装&#xff09;1.2.3 手动安装系统依赖1.2.3.1 CMake 3.11…

Linux驱动开发:SPI子系统

1、SPI简介 1.1 四根线 MISO&#xff1a;主设备数据输入&#xff0c;从设备数据输出。 MOSI&#xff1a;主设备数据输出&#xff0c;从设备数据输入。 SCLK&#xff1a;时钟信号&#xff0c;由主设备产生。 CS&#xff1a; 从设备片选信号&#xff0c;由主设备控制。 1.2…

《string类的使用介绍》

本文主要介绍string的常见的接口的使用 文章目录 一、什么是string类二、string类的使用1、string类对象的常见构造2、string类对象的容量操作3、string类对象的访问及遍历操作①operator[ ]的用法②迭代器③范围for 4、string类对象的修改操作①push_back②append③operator&a…