天津建设招标网站首页,wordpress小说站数据,无锡企业网银,企业网站创建步一、概述 如果说收集算法是内存回收的方法论#xff0c;那么垃圾收集器就是内存回收的具体实现。Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定#xff0c;因此不同的厂商、版本的虚拟机所提供的垃圾收集器都可能会有很大差别#xff0c;并且一般都会提供参数供用… 一、概述 如果说收集算法是内存回收的方法论那么垃圾收集器就是内存回收的具体实现。Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定因此不同的厂商、版本的虚拟机所提供的垃圾收集器都可能会有很大差别并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。接下来讨论的收集器基于JDK1.7 Update 14 之后的HotSpot虚拟机在此版本中正式提供了商用的G1收集器之前G1仍处于实验状态该虚拟机包含的所有收集器如下图所示 上图展示了7种作用于不同分代的收集器如果两个收集器之间存在连线就说明它们可以搭配使用。 虚拟机所处的区域则表示它是属于新生代收集器还是老年代收集器。Hotspot实现了如此多的收集器正是因为目前并无完美的收集器出现只是选择对具体应用最适合的收集器。 1、评估GC的性能指标 吞吐量: 运行用户代码的时间占总时间的比例 (总运行时间: 程序运行时间内存回收时间) 垃圾收集开销: 吞吐量的补数,垃圾收集器所用时间与总时间的比例 暂停时间: 执行垃圾收集时,程序的工作线程被暂停的时间 收集频率: 相对于应用程序的执行,收集操作发生的频率 内存占用: Java堆区所占的内存大小 快速: 一个对象从诞生到被回收所经历的时 2、并行和并发 并行Parallel指多条垃圾收集线程并行工作但此时用户线程仍然处于等待状态。 并发Concurrent指用户线程与垃圾收集线程同时执行但不一定是并行的可能会交替执行用户程序在继续运行。而垃圾收集程序运行在另一个CPU上。 3、吞吐量Throughput 吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值即 吞吐量 运行用户代码时间 /运行用户代码时间 垃圾收集时间。 假设虚拟机总共运行了100分钟其中垃圾收集花掉1分钟那吞吐量就是99%。 4、Minor GC 和 Full GC 新生代GCMinor GC指发生在新生代的垃圾收集动作因为Java对象大多都具备朝生夕灭的特性所以Minor GC非常频繁一般回收速度也比较快。具体原理见上一篇文章。 老年代GCMajor GC / Full GC指发生在老年代的GC出现了Major GC经常会伴随至少一次的Minor GC但非绝对的在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程。Major GC的速度一般会比Minor GC慢10倍以上。 Full GC定义是相对明确的就是针对整个新生代、老生代、元空间metaspacejava8以上版本取代perm gen的全局范围的GC 5、七种经典的垃圾回收器 串行回收器: Serial,Serial Old 并行回收器: ParNew,Parallel Scavenge,Parallel Old 并发回收器: CMS,G1 新生代收集器: Serial,ParNew,Parallel Scavenge 老年代收集器: Serial Old,Paralled Old,CMS 整堆收集器: G1 二、新生代收集器 1、Serial收集器 Serial串行收集器是最基本、发展历史最悠久的收集器它是采用复制算法的新生代收集器曾经JDK 1.3.1之前是虚拟机新生代收集的唯一选择。 它是一个单线程收集器只会使用一个CPU或一条收集线程去完成垃圾收集工作更重要的是它在进行垃圾收集时必须暂停其他所有的工作线程直至Serial收集器收集结束为止“Stop The World”。 这项工作是由虚拟机在后台自动发起和自动完成的在用户不可见的情况下把用户正常工作的线程全部停掉这对很多应用来说是难以接收的。这篇推荐看下。 下图展示了Serial 收集器老年代采用Serial Old收集器的运行过程 为了消除或减少工作线程因内存回收而导致的停顿HotSpot虚拟机开发团队在JDK 1.3之后的Java发展历程中研发出了各种其他的优秀收集器这些将在稍后介绍。但是这些收集器的诞生并不意味着Serial收集器已经“老而无用”实际上到现在为止它依然是HotSpot虚拟机运行在Client模式下的默认的新生代收集器。它也有着优于其他收集器的地方简单而高效与其他收集器的单线程相比对于限定单个CPU的环境来说Serial收集器由于没有线程交互的开销专心做垃圾收集自然可以获得更高的单线程收集效率。在用户的桌面应用场景中分配给虚拟机管理的内存一般不会很大收集几十兆甚至一两百兆的新生代仅仅是新生代使用的内存桌面应用基本不会再大了停顿时间完全可以控制在几十毫秒最多一百毫秒以内只要不频繁发生这点停顿时间可以接收。所以Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择。 2、ParNew 收集器 ParNew收集器就是Serial收集器的多线程版本它也是一个新生代收集器。除了使用多线程进行垃圾收集外其余行为包括Serial收集器可用的所有控制参数、收集算法复制算法、Stop The World、对象分配规则、回收策略等与Serial收集器完全相同两者共用了相当多的代码。 ParNew收集器的工作过程如下图老年代采用Serial Old收集器 ParNew收集器除了使用多线程收集外其他与Serial收集器相比并无太多创新之处但它却是许多运行在Server模式下的虚拟机中首选的新生代收集器其中有一个与性能无关的重要原因是除了Serial收集器外目前只有它能和CMS收集器Concurrent Mark Sweep配合工作CMS收集器是JDK 1.5推出的一个具有划时代意义的收集器具体内容将在稍后进行介绍。 ParNew 收集器在单CPU的环境中绝对不会有比Serial收集器有更好的效果甚至由于存在线程交互的开销该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证可以超越。 在多CPU环境下随着CPU的数量增加它对于GC时系统资源的有效利用是很有好处的。它默认开启的收集线程数与CPU的数量相同在CPU非常多的情况下可使用-XX:ParallerGCThreads参数设置。 3、Parallel Scavenge 收集器 Parallel Scavenge收集器也是一个并行的多线程新生代收集器它也使用复制算法。Parallel Scavenge收集器的特点是它的关注点与其他收集器不同CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间而Parallel Scavenge收集器的目标是达到一个可控制的吞吐量Throughput。 停顿时间越短就越适合需要与用户交互的程序良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用CPU时间尽快完成程序的运算任务主要适合在后台运算而不需要太多交互的任务。 Parallel Scavenge收集器除了会显而易见地提供可以精确控制吞吐量的参数还提供了一个参数-XX:UseAdaptiveSizePolicy这是一个开关参数打开参数后就不需要手工指定新生代的大小-Xmn、Eden和Survivor区的比例-XX:SurvivorRatio、晋升老年代对象年龄-XX:PretenureSizeThreshold等细节参数了。 虚拟机会根据当前系统的运行情况收集性能监控信息动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量这种方式称为GC自适应的调节策略GC Ergonomics。自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。 另外值得注意的一点是Parallel Scavenge收集器无法与CMS收集器配合使用所以在JDK 1.6推出Parallel Old之前如果新生代选择Parallel Scavenge收集器老年代只有Serial Old收集器能与之配合使用。 三、老年代收集器 1、Serial Old收集器 Serial Old 是 Serial收集器的老年代版本它同样是一个单线程收集器使用“标记-整理”Mark-Compact算法。 此收集器的主要意义也是在于给Client模式下的虚拟机使用。如果在Server模式下它还有两大用途 在JDK1.5 以及之前版本Parallel Old诞生以前中与Parallel Scavenge收集器搭配使用。 作为CMS收集器的后备预案在并发收集发生Concurrent Mode Failure时使用。 它的工作流程与Serial收集器相同这里再次给出Serial/Serial Old配合使用的工作流程图 2、Parallel Old收集器 Parallel Old收集器是Parallel Scavenge收集器的老年代版本使用多线程和“标记-整理”算法。前面已经提到过这个收集器是在JDK 1.6中才开始提供的在此之前如果新生代选择了Parallel Scavenge收集器。 老年代除了Serial Old以外别无选择所以在Parallel Old诞生以后“吞吐量优先”收集器终于有了比较名副其实的应用组合在注重吞吐量以及CPU资源敏感的场合都可以优先考虑Parallel Scavenge加Parallel Old收集器。 Parallel Old收集器的工作流程与Parallel Scavenge相同这里给出Parallel Scavenge/Parallel Old收集器配合使用的流程图 3、CMS收集器 CMSConcurrent Mark Sweep收集器是一种以获取最短回收停顿时间为目标的收集器它非常符合那些集中在互联网站或者B/S系统的服务端上的Java应用这些应用都非常重视服务的响应速度。从名字上“Mark Sweep”就可以看出它是基于“标记-清除”算法实现的。 CMS收集器工作的整个流程分为以下4个步骤 初始标记CMS initial mark仅仅只是标记一下GC Roots能直接关联到的对象速度很快需要“Stop The World”。 并发标记CMS concurrent mark进行GC Roots Tracing的过程在整个过程中耗时最长。 重新标记CMS remark为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录这个阶段的停顿时间一般会比初始标记阶段稍长一些但远比并发标记的时间短。此阶段也需要“Stop The World”。 并发清除CMS concurrent sweep 由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作。 所以从总体上来说CMS收集器的内存回收过程是与用户线程一起并发执行的。通过下图可以比较清楚地看到CMS收集器的运作步骤中并发和需要停顿的时间 优点 CMS是一款优秀的收集器它的主要优点在名字上已经体现出来了并发收集、低停顿因此CMS收集器也被称为并发低停顿收集器Concurrent Low Pause Collector。 缺点 对CPU资源非常敏感 其实面向并发设计的程序都对CPU资源比较敏感。在并发阶段它虽然不会导致用户线程停顿但会因为占用了一部分线程或者说CPU资源而导致应用程序变慢总吞吐量会降低。 CMS默认启动的回收线程数是CPU数量3/4也就是当CPU在4个以上时并发回收时垃圾收集线程不少于25%的CPU资源并且随着CPU数量的增加而下降。但是当CPU不足4个时比如2个CMS对用户程序的影响就可能变得很大如果本来CPU负载就比较大还要分出一半的运算能力去执行收集器线程就可能导致用户程序的执行速度忽然降低了50%其实也让人无法接受。 无法处理浮动垃圾Floating Garbage 可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。 由于CMS并发清理阶段用户线程还在运行着伴随程序运行自然就还会有新的垃圾不断产生。这一部分垃圾出现在标记过程之后CMS无法再当次收集中处理掉它们只好留待下一次GC时再清理掉。 这一部分垃圾就被称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行那也就还需要预留有足够的内存空间给用户线程使用因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集需要预留一部分空间提供并发收集时的程序运作使用。 标记-清除算法导致的空间碎片 CMS是一款基于“标记-清除”算法实现的收集器这意味着收集结束时会有大量空间碎片产生。 空间碎片过多时将会给大对象分配带来很大麻烦往往出现老年代空间剩余但无法找到足够大连续空间来分配当前对象。 四、G1收集器 G1Garbage-First收集器是当今收集器技术发展最前沿的成果之一它是一款面向服务端应用的垃圾收集器HotSpot开发团队赋予它的使命是在比较长期的未来可以替换掉JDK 1.5中发布的CMS收集器。与其他GC收集器相比G1具备如下特点 并行与并发 G1 能充分利用多CPU、多核环境下的硬件优势使用多个CPU来缩短“Stop The World”停顿时间部分其他收集器原本需要停顿Java线程执行的GC动作G1收集器仍然可以通过并发的方式让Java程序继续执行。 分代收集 与其他收集器一样分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆但它能够采用不同方式去处理新创建的对象和已存活一段时间、熬过多次GC的旧对象来获取更好的收集效果。 空间整合 G1从整体来看是基于“标记-整理”算法实现的收集器从局部两个Region之间上来看是基于“复制”算法实现的。这意味着G1运行期间不会产生内存空间碎片收集后能提供规整的可用内存。此特性有利于程序长时间运行分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。 可预测的停顿 这是G1相对CMS的一大优势降低停顿时间是G1和CMS共同的关注点但G1除了降低停顿外还能建立可预测的停顿时间模型能让使用者明确指定在一个长度为M毫秒的时间片段内消耗在GC上的时间不得超过N毫秒这几乎已经是实时JavaRTSJ的垃圾收集器的特征了。 横跨整个堆内存 在G1之前的其他收集器进行收集的范围都是整个新生代或者老生代而G1不再是这样。 G1在使用时Java堆的内存布局与其他收集器有很大区别它将整个Java堆划分为多个大小相等的独立区域Region虽然还保留新生代和老年代的概念但新生代和老年代不再是物理隔离的了而都是一部分Region不需要连续的集合。 建立可预测的时间模型 G1收集器之所以能建立可预测的停顿时间模型是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小回收所获得的空间大小以及回收所需时间的经验值在后台维护一个优先列表每次根据允许的收集时间优先回收价值最大的Region这也就是Garbage-First名称的来由。这种使用Region划分内存空间以及有优先级的区域回收方式保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。 避免全堆扫描——Remembered Set G1把Java堆分为多个Region就是“化整为零”。但是Region不可能是孤立的一个对象分配在某个Region中可以与整个Java堆任意的对象发生引用关系。在做可达性分析确定对象是否存活的时候需要扫描整个Java堆才能保证准确性这显然是对GC效率的极大伤害。 为了避免全堆扫描的发生虚拟机为G1中每个Region维护了一个与之对应的Remembered Set。虚拟机发现程序在对Reference类型的数据进行写操作时会产生一个Write Barrier暂时中断写操作。 检查Reference引用的对象是否处于不同的Region之中在分代的例子中就是检查是否老年代中的对象引用了新生代中的对象如果是便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。 如果不计算维护Remembered Set的操作G1收集器的运作大致可划分为以下几个步骤 初始标记Initial Marking 仅仅只是标记一下GC Roots 能直接关联到的对象并且修改TAMSNest Top Mark Start的值让下一阶段用户程序并发运行时能在正确可以的Region中创建对象此阶段需要停顿线程但耗时很短。 并发标记Concurrent Marking 从GC Root 开始对堆中对象进行可达性分析找到存活对象此阶段耗时较长但可与用户程序并发执行。 最终标记Final Marking 为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录虚拟机将这段时间对象变化记录在线程的Remembered Set Logs里面最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中这阶段需要停顿线程但是可并行执行。 筛选回收Live Data Counting and Evacuation 首先对各个Region中的回收价值和成本进行排序根据用户所期望的GC 停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行但是因为只回收一部分Region时间是用户可控制的而且停顿用户线程将大幅度提高收集效率。 通过下图可以比较清楚地看到G1收集器的运作步骤中并发和需要停顿的阶段Safepoint处 五、总结