今天吃完饭,吃撑了,也不想写代码,就写着写一篇博客。就来写一个jvm垃圾收集器相关的吧
java
JVM规范对于垃圾收集器的应该如何实现没有任何规定,所以不一样的厂商、不一样版本的虚拟机所提供的垃圾收集器差异较大,这里只看HotSpot虚拟机。 就像没有最好的算法同样,垃圾收集器也没有最好,只有最合适。咱们能作的就是根据具体的应用场景选择最合适的垃圾收集器。算法
Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了(新生代采用复制算法,老生代采用标志整理算法)。你们看名字就知道这个收集器是一个单线程收集器了。 它的 “单线程” 的意义不只仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工做,更重要的是它在进行垃圾收集工做的时候必须暂停其余全部的工做线程( "Stop The World" :将用户正常工做的线程所有暂停掉),直到它收集结束。 看图理解:浏览器
上图中:服务器
当它进行GC工做的时候,虽然会形成Stop-The-World,正如每种算法都有存在的缘由,该串行收集器也有存在的缘由:由于简单而高效(与其余收集器的单线程比),对于限定单个CPU的环境来讲,没有线程交互的开销,专心作GC,天然能够得到最高的单线程效率。因此Serial收集器对于运行在client模式下的应用是一个很好的选择(到目前为止,它依然是虚拟机运行在client模式下的默认新生代收集器) 串行收集器的缺点很明显,虚拟机的开发者固然也是知道这个缺点的,因此一直都在缩减Stop The World的时间。 在后续的垃圾收集器设计中停顿时间在不断缩短(可是仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)多线程
添加该参数来显式的使用串行垃圾收集器: "-XX:+UseSerialGC"闭包
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其他行为(控制参数、收集算法、回收策略等等)和Serial收集器彻底同样。 它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,目前只有它能与CMS收集器配合工做。 CMS收集器是一个被认为具备划时代意义的并发收集器,所以若是有一个垃圾收集器能和它一块儿搭配使用让其更加完美,那这个收集器必然也是一个不可或缺的部分了。 收集器的运行过程以下图所示: 并发
在Server模式下,ParNew收集器是一个很是重要的收集器,由于除Serial外,目前只有它能与CMS收集器配合工做; 但在单个CPU环境中,不会比Serail收集器有更好的效果,由于存在线程交互开销。框架
指定使用CMS后,会默认使用ParNew做为新生代收集: "-XX:+UseConcMarkSweepGC" 强制指定使用ParNew:
"-XX:+UseParNewGC" 指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相: "-XX:ParallelGCThreads"jvm
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。 Parallel Scavenge收集器关注点是吞吐量(如何高效率的利用CPU)。 CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提升用户体验)。 所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。(吞吐量:CPU用于用户代码的时间/CPU总消耗时间的比值,即=运行用户代码的时间/(运行用户代码时间+垃圾收集时间)。好比,虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。) 运行示意图: 布局
Parallel Scavenge收集器提供了不少参数供用户找到最合适的停顿时间或最大吞吐量,若是对于收集器运做不太了解的话,不进行手工优化,能够选择把内存管理优化交给虚拟机去完成。
Parallel Scavenge收集器提供两个参数用于精确控制吞吐量:
控制最大垃圾收集停顿时间 "-XX:MaxGCPauseMillis"
控制最大垃圾收集停顿时间,大于0的毫秒数; MaxGCPauseMillis设置得稍小,停顿时间可能会缩短,但也可能会使得吞吐量降低;由于可能致使垃圾收集发生得更频繁; 设置垃圾收集时间占总时间的比率 "-XX:GCTimeRatio"
设置垃圾收集时间占总时间的比率,0 < n < 100的整数; GCTimeRatio至关于设置吞吐量大小; 垃圾收集执行时间占应用程序执行时间的比例的计算方法是: 1 / (1 + n) 。 例如,选项-XX:GCTimeRatio=19,设置了垃圾收集时间占总时间的5% = 1/(1+19);默认值是1% = 1/(1+99),即n=99; 垃圾收集所花费的时间是年轻一代和老年代收集的总时间; 若是没有知足吞吐量目标,则增长代的内存大小以尽可能增长用户程序运行的时间;
Serial收集器的老年代版本,它一样是一个单线程收集器。 它主要有两大用途:一种用途是在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用,另外一种用途是做为CMS收集器的后备方案
Parallel Scavenge收集器的老年代版本。 使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,均可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。 在JDK1.6才有的。
指定使用Parallel Old收集器: "-XX:+UseParallelOldGC"
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它很是适合在注重用户体验的应用上使用。
从名字中的Mark Sweep这两个词能够看出,CMS收集器是一种 “标记-清除”算法实现的,它的运做过程相比于前面几种垃圾收集器来讲更加复杂一些。整个过程可分为四个步骤:
设置参数 指定使用CMS收集器 "-XX:+UseConcMarkSweepGC"
面向并发设计的程序都对CPU资源比较敏感(并发程序的特色)。在并发阶段,它虽然不会致使用户线程停顿,但会由于占用了一部分线程(或者说CPU资源)而致使应用程序变慢,总吞吐量会下降。(在对帐系统中,不适合使用CMS收集器)。 CMS的默认收集线程数量是=(CPU数量+3)/4; 当CPU数量越多,回收的线程占用CPU就少。 也就是当CPU在4个以上时,并发回收时垃圾收集线程很多于25%的CPU资源,对用户程序影响可能较大;不足4个时,影响更大,可能没法接受。(好比 CPU=2时,那么就启动一个线程回收,占了50%的CPU资源。) (一个回收线程会在回收期间一直占用CPU资源)
没法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败 在并发清除时,用户线程新产生的垃圾,称为浮动垃圾;
因为CMS是基于“标记+清除”算法来回收老年代对象的,所以长时间运行后会产生大量的空间碎片问题,可能致使新生代对象晋升到老生代失败。 因为碎片过多,将会给大对象的分配带来麻烦。所以会出现这样的状况,老年代还有不少剩余的空间,可是找不到连续的空间来分配当前对象,这样不得不提早触发一次Full GC。
为了解决空间碎片问题,CMS收集器提供−XX:+UseCMSCompactAlFullCollection标志,使得CMS出现上面这种状况时不进行Full GC,而开启内存碎片的合并整理过程; 但合并整理过程没法并发,停顿时间会变长; 默认开启(但不会进行,须要结合CMSFullGCsBeforeCompaction使用);
整体来看,CMS与Parallel Old垃圾收集器相比,CMS减小了执行老年代垃圾收集时应用暂停的时间; 但却增长了新生代垃圾收集时应用暂停的时间、下降了吞吐量并且须要占用更大的堆空间; (缘由:CMS不进行内存空间整理节省了时间,可是可用空间再也不是连续的了,垃圾收集也不能简单的使用指针指向下一次可用来为对象分配内存的地址了。相反,这种状况下,须要使用可用空间列表。即,会建立一个指向未分配区域的列表,每次为对象分配内存时,会从列表中找到一个合适大小的内存区域来为新对象分配内存。这样作的结果是,老年代上的内存的分配比简单实用碰撞指针分配内存消耗大。这也会增长年轻代垃圾收集的额外负担,由于老年代中的大部分对象是在新生代垃圾收集的时候重新生代提高为老年代的。) 当新生代对象没法分配过大对象,就会放到老年代进行分配。
上一代的垃圾收集器(串行serial, 并行parallel, 以及CMS)都把堆内存划分为固定大小的三个部分: 年轻代(young generation), 年老代(old generation), 以及持久代(permanent generation)。
G1(Garbage-First)是JDK7-u4才推出商用的收集器;
G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器。以极高几率知足GC停顿时间要求的同时,还具有高吞吐量性能特征。被视为JDK1.7中HotSpot虚拟机的一个重要进化特征。
G1的使命是在将来替换CMS,而且在JDK1.9已经成为默认的收集器。
G1能充分利用CPU、多核环境下的硬件优点,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其余收集器本来须要停顿Java线程执行的GC动做,G1收集器仍然能够经过并发的方式让java程序继续执行。
虽然G1能够不须要其余收集器配合就能独立管理整个GC堆,可是仍是保留了分代的概念。
与CMS的“标记--清理”算法不一样,G1从总体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
(火车算法是分代收集器所用的算法,目的是在成熟对象空间中提供限定时间的渐进收集。在后面一篇中会专门介绍)
这是G1相对于CMS的另外一个大优点,下降停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能创建可预测的停顿时间模型。能够明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒。在低停顿的同时实现高吞吐量。
能够有计划地避免在Java堆的进行全区域的垃圾收集; G1收集器将内存分大小相等的独立区域(Region),新生代和老年代概念保留,可是已经再也不物理隔离。 G1跟踪各个Region得到其收集价值大小,在后台维护一个优先列表; 每次根据容许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来); 这就保证了在有限的时间内能够获取尽量高的收集效率;
一个Region不多是孤立的,一个Region中的对象可能被其余任意Region中对象引用,判断对象存活时,是否须要扫描整个Java堆才能保证准确? 在其余的分代收集器,也存在这样的问题(而G1更突出):回收新生代也不得不一样时扫描老年代? 这样的话会下降Minor GC的效率;
不管G1仍是其余分代收集器,JVM都是使用Remembered Set来避免全局扫描: 每一个Region都有一个对应的Remembered Set; 每次Reference类型数据写操做时,都会产生一个Write Barrier暂时中断操做; 而后检查将要写入的引用指向的对象是否和该Reference类型数据在不一样的Region(其余收集器:检查老年代对象是否引用了新生代对象); 若是不一样,经过CardTable把相关引用信息记录到引用指向对象的所在Region对应的Remembered Set中; 当进行垃圾收集时,在GC根节点的枚举范围加入Remembered Set; 就能够保证不进行全局扫描,也不会有遗漏。
具体什么状况下应用G1垃圾收集器比CMS好,能够参考如下几点(但不是绝对): 超过50%的Java堆被活动数据占用; 对象分配频率或年代的提高频率变化很大; GC停顿时间过长(长于0.5至1秒); 建议: 若是如今采用的收集器没有出现问题,不用急着去选择G1; 若是应用程序追求低停顿,能够尝试选择G1; 是否代替CMS只有须要实际场景测试才知道。(若是使用G1后发现性能尚未使用CMS好,那么仍是选择CMS比较好)
能够经过下面的参数,来设置一些G1相关的配置。 指定使用G1收集器: "-XX:+UseG1GC"
当整个Java堆的占用率达到参数值时,开始并发标记阶段;默认为45: "-XX:InitiatingHeapOccupancyPercent"
为G1设置暂停时间目标,默认值为200毫秒: "-XX:MaxGCPauseMillis"
设置每一个Region大小,范围1MB到32MB;目标是在最小Java堆时能够拥有约2048个Region: "-XX:G1HeapRegionSize"
新生代最小值,默认值5%: "-XX:G1NewSizePercent"
新生代最大值,默认值60%: "-XX:G1MaxNewSizePercent"
设置STW期间,并行GC线程数: "-XX:ParallelGCThreads"
设置并发标记阶段,并行执行的线程数: "-XX:ConcGCThreads"
G1在标记过程当中,每一个区域的对象活性都被计算,在回收时候,就能够根据用户设置的停顿时间,选择活性较低的区域收集,这样既能保证垃圾回收,又能保证停顿时间,并且也不会下降太多的吞吐量。Remark(从新标记)阶段新算法的运用,以及收集过程当中的压缩,都弥补了CMS不足。 引用Oracle官网的一句话:“G1 is planned as the long term replacement for the Concurrent Mark-Sweep Collector (CMS)”。 G1计划做为并发标记-清除收集器(CMS)的长期替代品