当前位置: 首页 > news >正文

垃圾收集器

了解了垃圾收集算法有复制算法、标记–清除算法和标记–压缩算法。此时相当于对垃圾收集的理解还处于一种理论状态,相当于只定义了接口,还没有完成实现细节。Hotspot虚拟机提供了多种垃圾收集器,每种收集器都有各自的特点,虽然我们要对各个收集器进行比较,但并非为了挑选一个最好的,而是针对具体应用选择最合适的。

关于垃圾收集器的性能指标主要有两点:

  1. 停顿时间:因为GC而导致程序不能工作的时间长度
  2. 吞吐量:在特定的时间周期内一个应用的工作量的最大值。对关注吞吐量的应用来说长暂停时间是可以接受的,由于高吞吐量的应用关注的基准在更长周期时间上,所以快速响应时间不在考虑之内。

上图是HotSpot虚拟机中的7个垃圾收集器,连线表示垃圾收集器可以配合使用。

这里需要注意的是:G1收集器可以回收年轻代,也可以回收老年代,而其他的收集器只能针对特定代内存进行回收。

一. 串型收集器

1.1 serial 收集器

serial收集器是最基本,发展历史最悠久的收集器。

client模式下默认收集器配置就是串型收集器,因为在客户端模式下,分配给虚拟机管理的内存一般来说不会很大,Serial收集器收集几十M甚至一两百M的年轻代停顿时间可以控制在一百多ms内,只要不是太频繁,这点停顿是可以接受的。

串型收集器采用单线程stop-the-world的方式进行收集,当内存不足时,串型GC设置停顿标识,待所有线程都进入安全点(safePoint),应用线程暂停,串型GC开始工作,采用单线程方式回收空间并整理内存。

单线程意味着复杂度更低,占用内存更少,垃圾回收效率高,但同时也意味着不能有效利用多核优势。事实上,串型收集器特别适合堆内存不高,单核甚至双核CPU的场景。

开启方式:

-XX:+UseSerialGC

打开此开关后,使用Serial + Serial Old收集器组合来进行内存回收。

Serial Old收集器是Serial收集器的老年代版本,通常也是给client模式下的虚拟机使用,如果在Server模式下:

  1. 在jdk1.5 以及之前的版本中与Parallel Scavenge收集器搭配使用(因为Parallel Old收集器此时还未面世)
  2. 作为CMS收集器的后备预案,在并发收集发生失败时使用(Concurrent Mode Failure)

二. 并发收集器

-XX:+UseParallelGC 开启,使用Parallel Scavenge + Serial Old收集器组合来进行内存回收

-XX:+UseParallelOldGC开启,使用Parallel Scavenge + Parallel Old收集器组合来进行内存回收

其他收集器都是以关注停顿时间为目标,而并行收集器是以吞吐量为目标的。

  1. 停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。
  2. 而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

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

2.1 ParNew 收集器

ParNew是Serial的多线程版本,由多条GC线程并行进行垃圾清理,但是清理过程依然需要stop-the-world

ParNew追求 低停顿时间,与Serial唯一区别就是使用了多线程进行垃圾收集,在多CPU环境下性能比Serial会有一定程度的提升,但是线程切换需要额外的开销,因此单CPU下性能不如Serial。

2.2 Parallel Scavenge 垃圾收集器

Parallel Scavenge收集器提供了两个参数用于控制吞吐量:

  1. -XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间,收集器尽可能的保证回收时间不超过设定值
  2. -XX:GCTimeRatio:直接设置吞吐量大小的(0~100的整数)

缩短停顿时间的原理就是以牺牲吞吐量和年轻代空间来换取的:年轻代空间变小,垃圾回收就越频繁,导致吞吐量下降。

Parallel Scavenge收集器还提供了一个参数:-XX:+UseAdaptiveSizePolicy,用于打开系统自适应的调节策略。不需要手动设置年轻代,Eden和Survivor区的比例及晋升老年代对象的年龄,虚拟机会自动根据当前系统的运行情况收集性能监控信息,动态调整这些参数使其能够拥有最合适的停顿时间和最大的吞吐量。

2.3 CMS 垃圾收集器

这是一种以获取最短停顿时间为目标的收集器。

回收机制:

  1. 初始标记:仅仅只是标记一下GCRoots能直接关联到的对象,速度很快,需要停顿。
  2. 并发标记:使用多条标记线程,与用户线程并发执行,此过程进行可达性分析,标记出所有废弃对象,速度很慢
  3. 重新标记:多条线程并发执行,将刚才并发标记过程中新出现的废弃对象标记出来,需要停顿
  4. 并发清除:只使用一条GC线程,与用户线程并发执行,清除刚刚标记的对象,这个过程很耗时

CMS特点:

  1. 吞吐量低:低停顿时间是以牺牲吞吐量为代价的,导致CPU利用率不高
  2. 无法处理浮动垃圾:浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾,这部分垃圾只能到下一次GC时才能进行回收。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着CMS收集不能像其他收集器那样等待老年代快满的时候再回收。
  3. 标记清除算法导致的空间碎片,往往出现老年代空间剩余,但无法找到足够大连续空间来分配对象,不得不提前出发Full GC。

2.4 G1收集器

G1是一款面向服务端应用的垃圾收集器,没有新生代和老年代的概念,而是将堆划分成一块块独立的区域,当要进行垃圾回收时,首先估计每个区域中垃圾的数量,每次从垃圾回收价值最大的区域中开始回收,因此可以获得最大的回收效率。

从整体上看,G1是基于 标记-整理 算法实现的收集器,从两个区域之间看是基于复制算法实现的,这意味着运行期间不会产生内存空间碎片。

每个区域都有一个remembered Set,用于记录本区域中所有对象引用的对象所在区域,进行可达性分析时,只要在GC Roots中再加上Remembered Set即可防止对整个堆内存进行遍历。

如果不计算维护 Remembered Set 的操作,G1 收集器的工作过程分为以下几个步骤:

  1. 初始标记:仅使用一条初始标记线程对所有与 GC Roots 直接关联的对象进行标记。需要停顿
  2. 并发标记:使用一条标记线程与用户线程并发执行。此过程进行可达性分析,速度很慢。
  3. 最终标记:使用多条标记线程并发执行。需要停顿
  4. 筛选回收:回收废弃对象,此时也要 Stop The World,并使用多条筛选回收线程并发执行。

2.5 总结一下

收集器

串行/并行/并发

年轻代/老年代

收集算法

目标

适用场景

Serial

串行

年轻代

复制

响应速度优先

单 CPU 环境下的 Client 模式

Serial Old

串行

老年代

标记-整理

响应速度优先

单 CPU 环境下的 Client 模式、CMS 的后备预案

ParNew

串行 + 并行

年轻代

复制算法

响应速度优先

多 CPU 环境时在 Server 模式下与 CMS 配合

Parallel Scavenge

串行 + 并行

年轻代

复制算法

吞吐量优先

在后台运算而不需要太多交互的任务

Parallel Old

串行 + 并行

老年代

标记-整理

吞吐量优先

在后台运算而不需要太多交互的任务

CMS

并行 + 并发

老年代

标记-清除

响应速度优先

集中在互联网站或 B/S 系统服务端上的 Java 应用

G1

并行 + 并发

年轻代 + 老年代

标记-整理 + 复制算法

响应速度优先

面向服务端应用,将来替换 CMS

三. 内存分配与回收策略

关于对象的内存分配,也就是在堆上分配。主要分配在年轻代的Eden区上,少数情况也可能直接分配在老年代中。

3.1 Minor GC

当Eden区空间不足时,会触发Minor GC

Minor GC发生在年轻代上,因为年轻代对象存活时间很短,因此Minor GC会频繁执行,执行速度也很快。

工作流程:

  1. 应用不断创建对象,通常都是分配在Eden区,当其空间不足时,会触发Minor GC,仍然被引用的对象存活下来,复制到JVM选择Survivor区域,而没有被引用的对象则被回收
  2. 经过一次Minor GC,Eden就会空闲下来,直到再次达到Minor GC触发条件,这时候另一个Survivor区域就会成为To区域,Eden区域的存活对象和From区域对象都会被复制到To区域,并且存活的年龄计数会被+1
  3. 类似第二步的过程会发生很多次,直到有对象年龄计数达到阈值,这时候就会发生所谓的晋升过程,超过阈值的对象会被晋升到老年代

3.2 Full GC

Full GC发生在老年代上,老年代对象和年轻代的相反,其存活时间长,因此Full GC很少执行,而且执行速度要比Minor GC慢很多。

3.2.1 内存分配策略
  1. 对象优先在Eden区分配
  2. 大对象直接进入老年代
  3. 长期存活的对象进入老年代
  4. 动态对象年龄判定:如果在Survivor区中相同年龄所有对象大小的总和大于Survivor空间的一半,则年龄大于等于该年龄的对象会直接进入老年代。
  5. 分配空间担保:在发生Minor GC之前,虚拟机先见擦好老年代最大可用的连续空间是否大于年轻代所有对象总空间,如果条件成立的话,那么Minor GC可以确认是安全的,如果不成立的话虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于,或者 HandlePromotionFailure 设置不允许冒险,那这时也要改为进行一次 Full GC。
3.2.2 Full GC的触发条件
  1. 手动调用System.gc() 方法。这个方法是建议虚拟机进行Full GC,所以不一定会执行
  2. 老年代空间不足
  3. 方法区空间不足
  4. Minor GC 的平均晋升空间大小大于老年代可用空间
  5. 对象大小大于 To 区和老年代的可用内存

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【Linux】(32)详解命名管道 | 日志管理 | 进程池2.0
  • 基于python的百度迁徙迁入、迁出数据分析(七)
  • C# 报表功能
  • Nginx隐藏欢迎页Welcome to CentOS
  • 百日筑基第四十五天-从JAVA8走到JAVA9
  • Spring的代理模式
  • Omit<T, K> 解释
  • 【电子数据取证】支持最新版微信、企业微信、钉钉等重点应用数据提取分析!
  • 网络安全知识讲解
  • C语言典型例题30
  • Vue 3 中,组件间传值有多种方式
  • 【知识】pytorch中的pinned memory和pageable memory
  • Android Fragment:详解,结合真实开发场景Navigation
  • Java开发笔记--通用基础数据校验的设计
  • 思科CCIE最新考证流程
  • $translatePartialLoader加载失败及解决方式
  • 2017届校招提前批面试回顾
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • Android开源项目规范总结
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • Docker: 容器互访的三种方式
  • Git的一些常用操作
  • Js基础知识(一) - 变量
  • Python中eval与exec的使用及区别
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • Tornado学习笔记(1)
  • windows下mongoDB的环境配置
  • 高度不固定时垂直居中
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 力扣(LeetCode)965
  • 我感觉这是史上最牛的防sql注入方法类
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 正则表达式
  • 如何正确理解,内页权重高于首页?
  • # 达梦数据库知识点
  • #100天计划# 2013年9月29日
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (bean配置类的注解开发)学习Spring的第十三天
  • (pycharm)安装python库函数Matplotlib步骤
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (二)hibernate配置管理
  • (机器学习的矩阵)(向量、矩阵与多元线性回归)
  • (利用IDEA+Maven)定制属于自己的jar包
  • (算法)前K大的和
  • (一)u-boot-nand.bin的下载
  • (转)Linq学习笔记
  • .gitignore文件设置了忽略但不生效
  • .NET : 在VS2008中计算代码度量值
  • .NET MAUI学习笔记——2.构建第一个程序_初级篇
  • .Net 访问电子邮箱-LumiSoft.Net,好用
  • .NET简谈互操作(五:基础知识之Dynamic平台调用)
  • .NET开发不可不知、不可不用的辅助类(一)
  • .NET委托:一个关于C#的睡前故事