深刻理解java虚拟机之垃圾收集器 深刻理解java虚拟机之java内存区域 深刻理解java虚拟机之对象真的死了吗 Java垃圾收集算法

 

  前言

  若是说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。java虚拟机规范中对垃圾收集器应该如何实现并无任何规定,所以不一样的厂商、不一样的版本的虚拟机所提供的垃圾收集器都有可能会有很大的区别,而且通常都会提供参数供用户根据本身的应用特色和要求组合出各个年代所使用的收集器。html

  相关系列博客:java

  上图中展现了不一样年龄代的收集器,其中Serial、ParNew和Parallel Scavenge收集器做用于新生代,CMS、Parallel Old 和 Serial Old做用于老年代,G1在新生代和老年代均可以使用。不一样的收集器之间若是有连线,则说明他们能够相互搭配使用。算法

  相关概念

  并行:指的是多条垃圾收集线程一块儿公共,可是此时用户工做线程仍处于等待状态。多线程

  并发:指的是用户线程和垃圾收集线程同时工做,也有多是交替执行,用户程序在继续执行,而垃圾收集程序运行与另外一个CPU上。并发

  吞吐量:吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。post

 

  Serial收集器

  Serial收集器是一款串行执行的收集器,它是历史最悠久,也是最基本的收集器,采用复制算法实现的新生代收集器。在jdk1.3之前,Serial收集器是新生代惟一的选择。它是一个单线程执行的收集器,工做时只会知用一个cpu或线程区执行,更重要的是Serial在工做期间必须停掉全部的用户线程,直至垃圾收集完成,这一过程咱们称之为“stop the world”。这项工做是由虚拟机自动执行和自动完成的,用户在不知情的状况下停掉了全部的线程,这对于一个最求响应速度来讲简直是没法接受的。下图展现了Serial收集器在工做时的运行流程:性能

  因为Serial收集器的工做模式是单线程的,天然就没有了多线程环境下线程切换带来的性能开销,因此该收集器在单线程环境下更加简单高效。网站

 

  ParNew 收集器

  Parnew是Serial收集器的多线程版本,也是新生代收集器。ParNew收集器和Serial收集器除了多线程工做外几乎是相同的,包括全部控制参数、收集算法、stop the world,对象分配规则,回收策略等都是同样的。运行流程以下图:url

  虽然与Serial收集器相比仅仅多了多线程特性外,没有其它的创新之处,可是它倒是许多Server模式下的虚拟机新生代收集器的首选,缘由在于目前为止只有Serial和ParNew两个新生代收集器可以与性能优异的CMS配合使用。关于CMS介绍将在下文展开描述。线程

 

  Parallel Scavenge 收集器

  Parallel Scavenge也是一款使用复制算法的新生代收集器。该收集器与其它收集器不一样的是,它关注的目标是达到一个可控制的吞吐量,而CMS等收集器的关注点则是尽量地减小用户线程地停顿时间,提升用户体验。Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。也所以,Parallel Scavenge 被成为“吞吐量优先”收集器。

  停顿时间越短就越适合与用户交互较多地程序,这样用户体验才更好。而高吞吐量则可让出更多的cpu资源给用户线程,让程序更快的完成运算任务,更适合后台运算较多而不须要与用户交互的程序。

  自适应调节策略是Parallel Scavenge收集器的特色,也是与ParNew收集器的区别。Parallel Scavenge经过打开-XX:+UseAdaptiveSizePolicy的设置,就不须要手动地调节新生代(-Xmn)大小,Eden和Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数,而是根据当前系统运行状况来肯定这些参数,从而提升程序地吞吐量和缩短停顿时间,这一过程称之为GC自适应的调节策略(GC Ergonomics)。

  另外值得注意的一点是,Parallel Scavenge没法已CMS配合使用,若是新生代选择了Parallel Scavenge收集器,那么老年代的收集器只能选用Serial Old或者Parallel Old来配置使用。

 

  Serial Old收集器

  Serial Old是Serial收集器的老年代版本,也是单线程工做的,使用的是“标记-整理”算法。

  该收集器主要用于Client模式下的虚拟机使用,若是在Server模式下能够与Parallel Scavenge收集器配合使用;做为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。运行流程以下:

 

  Parallel Old收集器

  Parallel Old是Parallel Scavenge的老年代版本,也是一个并行收集器,使用“标记-整理”算法。该收集器在jdk1.6后对外提供使用,Parallel Scavenge 和  Parallel Old配合使用的话,更加适合应用于高吞吐量和cpu敏感资源的场合。下面是这两个收集器配合使用的运行流程:

 

  CMS收集器

  CMS(Concurrent Mark Sweep)是一个并发收集器,使用了“标记-清除”算法来实现的。该收集器最求的更短的停顿时间,从而提高用户体验,所以也很是符合使用在网站、B/S系统的服务端的应用。

  CMS收集器的工做流程大概能够分为如下4个步骤:

  • 初始标记:这个阶段仅仅标记可以和gc roots直接关联的对象,速度很快,可是须要“stop the world”。
  • 并发标记:这个阶段开始进行gc roots tracing标记,与用户线程一块儿执行的,消耗时间不少。
  • 从新标记:这个阶段是要是修正在并发标记期间因为用户线程也在运行而产生标记变更的那部分对象的标记,比较耗费的时间比初始标记阶段要长,可是远比并发标记阶段要短,这个过程也是须要“stop the world”的。
  • 并发清除:对无用对象进行回收操做。这个过程与用户线程并行执行。

  因为标记和清除阶段能够和用户线程一块儿工做,所以几乎能够把CMS收集器的工做是并发的:

  CMS是一款优秀的收集器,它的主要优势是低停顿,并发收集,所以也被成为并发低停顿收集器(Concurrent Low Pause Collector)。

  固然,CMS收集器也有必定的缺点,主要包括一下几点:

  • CMS收集器使用“标记-清除”算法实现,所以不可避免地有内存碎片地问题。当内存碎片过多时,在分配大对象地过程当中即便有足够的空间,可是找不到足够地连续的空间来放该对象,那么就有可能触发一次full gc。
  • 没法处理浮动垃圾(Floating Garbage) 可能出现“Concurrent Mode Failure”失败而致使另外一次Full GC的产生。这是由于在标记的过程当中用户线程也在运行着,那么在这一过程当中出现的垃圾没法当即回收,而是等下一次gc才能清理,我这部分的垃圾就叫作“浮动垃圾”。也是因为在垃圾收集阶段用户线程还须要运行,那也就还须要预留有足够的内存空间给用户线程使用,所以CMS收集器不能像其余收集器那样等到老年代几乎彻底被填满了再进行收集,须要预留一部分空间提供并发收集时的程序运做使用。
  • 对cpu资源很是敏感。其实,只要是面对并发的状况下都会有这个问题,在并发阶段虽然不会中断用户线程,可是由于占用了部分用户的资源而致使程序变慢,总吞吐量下降。CMS搜集器默认的线程数 = (cpu核数 + 3) / 4,当cpu数量大于4时,垃圾回收线程数很多于25%,随着线程数的增长而降低,当cpu数量小于4时对线程的执行效率有显著的影响。

  运行示意图以下:

  

  G1收集器

  G1(Garbage-First)是一款面向服务端应用的垃圾收集器,JDK 7 Update4 后开始进入商用。HotSpot开发团队赋予它的使命是将来能够替换掉JDK 1.5中发布的CMS收集器。以前提供的收集器都是仅做用于新生代或者是老年代,可是G1收集器能够做用于新生代和老年代,由于使用G1收集器是java heap的内存结构有很大的不一样,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,可是他们已经没有了物理上的隔阂了,它们都是region的一部分的集合。

  G1(Garbage-First)收集器是当今收集器技术发展的最前沿成果之一,与其余收集器相比,G1收集器具备如下特征:

  • 并行与并发: G1能充分利用多CPU,多核环境下的硬件优点,使用多个CPU来缩短Stop-The-World停顿时间,部分其余收集器本来须要停顿java线程执行的GC动做,G1收集器仍然能够经过并发的方式让java程序继续执行。
  • 分代收集: 与其余收集器同样,分代概念在G1中仍然得以保留。虽然G1能够不须要其余收集器配合可以独立管理整个堆,但它可以采用不用的方式去处理新创的对象和已经存活了一段世纪那、熬过屡次GC的旧对象以得到更好的收集效果。
  • 空间整合: 与CMS的“标记-清除”算法不一样,G1总体来看采用了“标记-整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的。不管使用哪种方法,都意味着G1运做期间不会产生内存空间碎片的问题,收集后能提供规整的可用空间。这种特性有利于程序长时间运行,分配大对象是不会由于没法获得连续内存空间而提早处罚一次GC。
  • 可预测的停顿: 这是G1相对于CMS的另外一大优点,下降停顿时间是G1和CMS共同的关注点,但G1除了最求低停顿外,还能创建可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片断内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎是java(RTSJ)的垃圾收集器的特征了。

  G1收集器之因此可以创建可预测的停顿时间模型,由于他可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所得到的空间大小以及回收所须要的经验值),在后台维护一个优先表,每次根据容许的收集时间,优先回收价值最大的Region。这种使用Region划份内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内能够获取尽量高的收集效率。

  在G1收集器中,Region之间的对象引用以及其余收集器中的新生代和老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。G1中每一个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操做时,会产生一个Write Barrier暂时中断写操做,检查Reference引用的对象是否处于不一样的Region中,若是是,便经过CardTable把相关引用信息记录到被引用对象所属的Region中的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set便可保证不对全堆扫描,也不会有遗漏。

  若是不计算维护Remembered Set的操做,G1收集器的运做大体分为如下几个步骤:

  1. 初始标记(Initial Marking): 这阶段仅仅只是标记GC Roots能直接关联到的对象并修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确的可用的Region中建立新对象,这阶段须要停顿线程,可是耗时很短。
  2. 并发标记(Concurrent Marking): 从GC Roots 开始对堆的对象进行可达性分析,找出存活的对象,这阶段耗时长,可是能够与用户程序并发执行。
  3. 最终标记(Final Marking): 为了修正在并发标记期间由于用户程序继续运行而致使标记产生变更的那一部分标记记录,虚拟机将这段时间对象变化记录记录在线程Remembered Set Logs里面。
  4. 筛选回收(Live Data Counting and Evacuation): 首先对各个Region的回收价值和成本进行排序,根据用户所指望的GC停顿时间来制定回收计划,这一阶段是能够与用户程序一块儿并发执行的,可是由于只回收部分Region,时间是用户可控的,并且停顿用户线程将大幅度提升收集效率。

  执行流程以下图:

  

  总结

  

收集器 串行、并行or并发 新生代/老年代 算法 目标 适用场景
Serial 串行 新生代 复制算法 响应速度优先 单CPU环境下的Client模式
Serial Old 串行 老年代 标记-整理 响应速度优先 单CPU环境下的Client模式、CMS的后备预案
ParNew 并行 新生代 复制算法 响应速度优先 多CPU环境时在Server模式下与CMS配合
Parallel Scavenge 并行 新生代 复制算法 吞吐量优先 在后台运算而不须要太多交互的任务
Parallel Old 并行 老年代 标记-整理 吞吐量优先 在后台运算而不须要太多交互的任务
CMS 并发 老年代 标记-清除 响应速度优先 集中在互联网站或B/S系统服务端上的Java应用
G1 并发 both 标记-整理+复制算法 响应速度优先 面向服务端应用,未来替换CMS

 

  参考资料: 《深刻理解Java虚拟机-JVM高级特性与最佳实践》 -周志明

  喜欢我写的博客的同窗能够关注订阅号【Java解忧杂货铺】,里面不按期发布一些技术干活,也能够免费获取大量最新最流行的技术教学视频

相关文章
相关标签/搜索