垃圾收集器

上文说到了垃圾收集算法,这次我们聊一下HotSpot的具体垃圾收集器的实现,以JDK1.7为例,其包含的可选垃圾收集器如下图:

不同收集器之间的连线,代表它们可以搭配使用,收集器所属的区域代表它们属于新生代收集器还是老年代收集器,下面总结一下每个收集器的特点:

Serial 收集器

Serial 字面意思为串行,这与它的工作方式是有关系的,因为它是一个单线程收集器,他在新生代工作,采取的是复制算法,在单CPU的机器上可以高效的完成收集工作,其对于运行在Client模式下的虚拟机是一个很好的选择。

ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本,除了采用多线程进行垃圾收集之外,其余行为包括控制参数、收集算法、STW、对象分配法则、回收策略都与Serial收集器完全一样。由于采用多线程ParNew通常是虚拟机运行在Server模式下的首选新生代收集器,ParNew收集器能与CMS收集器配合工作,他默认开启的收集线程数与CPU的数目相同,可以使用-XX:ParallelGCThread 参数设施线程数量

Parallel Scavenge收集器

从名称可以看出Parallel Scavenge也是并行的多线程收集器,工作在新生代,采用复制算法进行收集,那和ParNew相比它有什么特别之处呢?与其他收集器的关注点不同,CMS等收集器的关注点是尽可能的缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是吞吐量,所谓吞吐量是指CPU用于运行用户代码的时间与CPU总消耗时间的比值。如果说实际应用场景,那么CMS等停顿时间较短的收集器适合需要与用户进行交互的程序,以获得良好的响应速度,而Parallel Scavenge收集器具有较高的吞吐量则可以高效的利用CPU时间,尽快完成运算任务,适合后台运算不需要太多交互的任务。
Parallel Scavenge收集器提供了两个参数可以用于精确控制吞吐,分别是最大垃圾收集停顿时间:-XX:MaxGCPauseMillis 以及知己设置吞吐量大小的-XX:FCGCTimeRatio参数,另外还提供了一个自动调节的参数:-XX:PretenureSize

Serial Old收集器

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

Parallel Old收集器

他是Parallel Scavenge 收集器的老年代版本,使用标记-整理算法,配合PS收集器使用。

CMS收集器 Concurrent Mark Sweep

CMS是一种以获取最短回收暂停时间为目标的收集器,从名称可以看出CMS是基于标记-整理算法实现的,它的运作过程比较复杂,整个过程分为4个步骤:

  1. 初始标记 initial mark
    这个阶段会扫描root对象直接关联的可达对象。注意不会递归的追踪下去,只是到达第一层而已。这个过程,会STW,但是时间很短。
  2. 并发标记 concurrent mark
    这个阶段是进行真正的GC Tracing,递归分析存活对象,无须STW
  3. 重新标记 remark
    需要STW,因为并发标记阶段,用户程序继续运行,所以重写标记那些产生变化的对象。
  4. 并发删除 concurrent sweep
    无须STW,对分析出的死亡对象进行清理。

CMS收集器的缺点

  1. 对CPU资源敏感

由于并行的特性,需要占用cpu资源,影响用户程序性能,CMS默认启动的回收线程是(CPU数量 + 3)/ 4 当CPU数量小于4个时,CPU数量越少垃圾回收线程占比越大。

  1. 无法处理浮动垃圾

Floating Garbage,可能会出现Concurrent Mode Failure,而导致另一次Full GC的产生,浮动垃圾是什么,由于并发删除阶段用户线程还在执行,自然还会产生新的垃圾,这部分垃圾在标记步骤之后,所以无法清理,只要等待下一次GC。由于并发的特性,CMS的不能像其他收集器那样等到老年代几乎完全被填满再进行收集,需要预留一部分空间提供并发收集时的用户程序使用,JDK1.5的默认设置是老年代使用了68%的空间后就会被激活,1.6为92%,这个比例可以通过参数 -XX:CMSInitiatingOccupancyFraction来调整,如果GC期间预留的内存空间无法满足用户程序要求,就会出现一次“Concurrent Mode Failure”,这时会启动后备预案,临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。

  1. 内存碎片

由于CMS采用标记-删除算法,会产生大量的内存碎片,造成分配大对象时,虽然老年代存在不少剩余空间,但找不到可用的内存空间,不得不提前触发一次Full GC,为了解决这个问题,CMS提供了一个-XX:UseCMSCompactAtFullCollection 开关参数,用于在CMS顶不住进行Full GC时开启内存碎片整理合并,由于整理过程无法并发,所以停顿时间不得不边长,另外还有一个参数-XX:CMDSFullGCsBeforeCompaction,这个参数是用于设置执行多少次不压缩的Full GC后,再进行一次带压缩的Full GC,默认为0,表示每次都进行碎片整理。

G1 收集器

Garbage First 是最前沿的收集器之一,在JDK1.7 u4开始商用,它具有如下特点:
1.并发与并行 充分利用多CPU和多核的硬件优势,来缩短STW停顿的时间
2.分代收集,且不需要其他收集器配合就可以独立管理整个Java堆
3.整体上采用标记-整理算法,内部不同Region之间采用复制算法。
4.可预测的停顿,使用者可以指定消耗在垃圾回收上的时间。

比较特别的是G1收集器将整个Java对划分为多个大小相等的独立区域Region,虽然仍然保留新生代和老年代,但已经不再有明显的物理界限,他们只是一部分Region的集合。G1会跟踪各个Region里的垃圾堆积价值大小(回收所获得的空间大小以及所需时间的经验值),维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

内存分配与回收策略

  1. 对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配,当Eden没有足够空间时,将发生一次MinorGC,虚拟机提供了-XX:PrintGCDetails这个参数,在虚拟机发生垃圾收集时打印内存回收日志,并在内存退出时输出当前的内存各区域分配情况。(MinorGC又称新生代GC,MajorGC/FullGC又称老年代C)。

  1. 大对象直接进入老年代

java虚拟机提供了一个-XX:PretenureSizeThreshold参数,设置直接在老年代分配的对象的大小,这样可以避免在Eden和两个Survivor区域之间发生大量内存复制。(这个参数只对Serial和ParNew收集器有效)

  1. 长期存活对象将进入老年代

虚拟机为每个对象定义了一个年龄(Age)计数器,如果对象在Eden区域出生并经过第一次MinorGC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1,对象在Survivor区域熬过一次MinorGC,年龄就加1,当年龄增加到一定程度(默认是15岁),就会被晋升为老年代,这个阀值可以通过参数:-XX:MaxTenuringThreshold设置。

  1. 动态对象年龄判定

为了更好的适应不同程序的内存情况,虚拟机并不是永远的要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。

  1. 空间分配担保

在发生MinorGC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的空间,如果这个条件成立,那么MinorGC就是安全的,如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败,如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次MinorGC,尽管这次MinorGC有风险;如果小于,或者HandlePromotionFailure设置不允许冒险,这是也要改为进行一次Full GC。

参考资料

本文参考:《深入理解Java虚拟机》

JVM学习笔记三:垃圾收集器及内存管理策略的更多相关文章

  1. 《深入java虚拟机》读书笔记之垃圾收集器与内存分配策略

    前言 该读书笔记用于记录在学习<深入理解Java虚拟机--JVM高级特性与最佳实践>一书中的一些重要知识点,对其中的部分内容进行归纳,或者是对其中不明白的地方做一些注释.主要是方便之后进行 ...

  2. JVM基础知识2 垃圾收集器与内存分配策略

    如何判断堆中的哪些对象可以被回收 主流的程序语言都是使用根搜索算法(GC Roots Tracing)判定对象是否存活 基本思路是:通过一系列名为“GC Roots”的对象作为起点,从这些节点开始向下 ...

  3. 深入理解JVM(三)垃圾收集器和内存分配策略

    3.1 关于垃圾收集和内存分配 垃圾收集和内存分配主要针对的区域是Java虚拟机中的堆和方法区: 3.2 如何判断对象是否“存活”(存活判定算法) 垃圾收集器在回收对象前判断其是否“存活”的两个算法: ...

  4. JVM学习笔记——GC垃圾收集器

    GC 垃圾收集器 Java 堆内存采用分代回收算法,因此 JVM 针对新生代和老年代提供了多种垃圾收集器. 1. Serial 收集器 Serial 收集器是单线程收集器,采用复制算法. 是最基本的垃 ...

  5. JVM系列2:垃圾收集器与内存分配策略

    垃圾收集是一个很大话题,本文也只是看了深入理解Java虚拟机总结了下垃圾收集的知识. 首先按照惯例,先上思维导图: 垃圾收集简而言之就是JVM帮我们清理掉内存区域不需要的数据.它主要负责清理堆中实例对 ...

  6. 《深入理解 Java 虚拟机》读书笔记:垃圾收集器与内存分配策略

    正文 垃圾收集器关注的是 Java 堆和方法区,因为这部分内存的分配和回收是动态的.只有在程序处于运行期间时才能知道会创建哪些对象,也才能知道需要多少内存. 虚拟机栈和本地方法栈则不需要过多考虑回收的 ...

  7. 《深入理解Java虚拟机》读书笔记:垃圾收集器与内存分配策略

    请移步至:http://zhanjindong.info/2014/05/18/java-gc/

  8. JVM学习笔记-第三章-垃圾收集器与内存分配策略

    JVM学习笔记-第三章-垃圾收集器与内存分配策略 tips:对于3.4之前的章节可见博客:https://blog.csdn.net/sanhewuyang/article/details/95380 ...

  9. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

随机推荐

  1. elasticSearch(5.3.0)的评分机制的研究

    1.  ElasticSearch的评分 在用ElasticSearch作为搜索引擎的时候,如果采用关键字进行查询,ElasticSearch会对每个符合查询条件的文档进行评分,在5.3.0的版本中, ...

  2. bootstrap-table前台和后台分页对json格式的要求

    Bootstrap是一款前端非常流行的框架,其中的表格更为大家经常使用.大家都知道表格的分页分为前台和后台分页,也就是表格配置中sidePagination属性,当sidePagination: &q ...

  3. Generic(泛型)

    什么是泛型:"通过参数化类型来实现在同一份代码上操作多种数据类型.利用"参数化类型"将类型抽象化,从而实现灵活的复用". 简单来说泛型就是为了使一些代码能够重复 ...

  4. 使用CodeDOM动态编译一个字符串表达式

    由于程序需要,计算的表达式使用字符串传输,这样对运算造成了影响.在程序中直接执行这段表达式可以得到值, 但是使用字符串就没有办法运算了, 所以想到用CodeDOM将这段字符串拼接在代码中编译 类似st ...

  5. Python库:序列化和反序列化模块pickle介绍

    1 前言 在“通过简单示例来理解什么是机器学习”这篇文章里提到了pickle库的使用,本文来做进一步的阐述. 通过简单示例来理解什么是机器学习 pickle是python语言的一个标准模块,安装pyt ...

  6. Unity 游戏框架搭建 (五) 简易消息机制

    什么是消息机制? 23333333,让我先笑一会. 为什么用消息机制?   三个字,解!!!!耦!!!!合!!!!. 我的框架中的消息机制用例: 1.接收者 ``` using UnityEngine ...

  7. IP协议详解

    Internet地址结构 表示IP地址 目前的IP版本有4和6. 目前最流行的就是IPv4,有十进制和二进制两种表示方法.分别是: 点分四组十进制.每一组范围是[0~255],如:255.255.25 ...

  8. 用VS Code体验调试.NET Core 2.0 Preview (传统三层架构)

    准备工作 VS Code下载地址:https://vscode.cdn.azure.cn/stable/379d2efb5539b09112c793d3d9a413017d736f89/VSCodeS ...

  9. SQL语言-----数据操作

    数据操作 增加数据,insert into 标准格式 insert into 表名 (字段的列表)value(数据列表): 使用set insert into 表名 set 字段1=值,2.....: ...

  10. Azure PaaS服务密钥的安全性

    Azure PaaS服务,比如存储,Redis缓存,服务总线,IoT中心等等,一般通过密钥来认证客户端,也就是说只有提供正确密钥的客户端才能访问和使用对应的Azure PaaS服务,所以这个密钥是很重 ...