垃圾收集器

上文说到了垃圾收集算法,这次我们聊一下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. 映射语句之INSERT语句

    1.一个 INSERT SQL 语句可以在<insert>元素在映射器 XML 配置文件中配置 例子: <insert id="insertStudentWithId&qu ...

  2. Log4j中配置日志文件相对路径

    方法一. 解决的办法自然是用相对路径代替绝对路径,其实log4j的FileAppender本身就有这样的机制,如:log4j.appender.logfile.File=${WORKDIR}/logs ...

  3. 利用Advanced Installer将asp.netMVC连同IIS服务和mysql数据库一块打包成exe安装包

    因为业务需要,项目中需要把asp.netmvc项目打包成exe安装程序给客户,让客户直接可以点下一步下一步安装部署web程序,并且同时要将IIS服务和mysql一同安装到服务器上,因为客户的电脑可能是 ...

  4. 2.如何安装vmvare tools

    1.在主页点击虚拟机 重装vmvaretools,接着就会下载tar.gz包 2.cd 到解压包的地方,解压sudo tar zxf ... 3.解压之后会生成一个vmvare-toos-distri ...

  5. Disruptor的应用示例——大文件拆分

    结合最近Disruptor的学习,和之前一直思考解决的大文件拆分问题,想到是否可以使用Disruptor作为生产者/消费者传递数据的通道呢?借助其高效的传递,理论上应当可以提升性能.此文便是此想法的落 ...

  6. JAVAEE——SSH项目实战01:SVN介绍、安装和使用方法

    1 学习目标 1.掌握svn服务端.svn客户端.svn eclipse插件安装方法 2.掌握svn的基本使用方法 2 svn介绍 2.1 项目管理中的版本控制问题 通常软件开发由多人协作开发,如果对 ...

  7. pyparsing:定制自己的解析器

    在工作中,经常需要解析不同类型的文件,常用的可能就是正则表达式了,简单点的,可能会使用awk.这里要推荐一种比较小众的方式,使用pyparsing来解析文件. pyparsing可以做些什么呢?主要可 ...

  8. 利用libcurl进行post

    逛百度知道看到有个人提问:http://zhidao.baidu.com/question/1447092283140740700 C写HTTP应用只有疯子才会老老实实的SOCKET编程吧?我后来还是 ...

  9. PostMessage,模拟键盘输入

    for i := 0 to length(tel) do           begin             //SendMessage(cHwnd,WM_KEYDOWN,Integer(tel[ ...

  10. Java之分支和循环

    Java中的分支语句: if语句: if语句的四种写法: (1) if(表达式_布尔值) { ... } (2) if(表达式_布尔值) { ... } else { ... } (3) if(表达式 ...