JVM探秘:垃圾收集器

本系列笔记主要基于《深刻理解Java虚拟机:JVM高级特性与最佳实践 第2版》,是这本书的读书笔记。java

垃圾收集器

垃圾收集算法是是内存回收的方法论,垃圾收集器是内存回收的具体实现。不一样的虚拟机会有不一样的垃圾收集器的实现,咱们主要讨论的是默认的HotSpot虚拟机,这个虚拟机包含的垃圾收集器以下图;算法

image

如上图所示,一共有7种垃圾收集器,若是两个垃圾收集器之间有双箭头连线,则两个垃圾收集器可搭配使用。上面是新生代的收集器,下面是老年代的收集器。每一个垃圾收集器都有各自适合的使用场景。数据结构

Serial 收集器

Serial是一个“单线程”的新生代收集器,使用复制算法,它只会使用一个CPU或者一条收集器线程去完成垃圾收集工做,而且它在垃圾收集时,必须暂停全部其余的工做线程,直到它收集结束。“Stop The World”会在用户不可见的状况下,把用户的工做线程所有停掉,这每每是使人难以接受的。多线程

下图是 Serial/Serial Old 收集器运行示意图:并发

image

上图中,新生代是Serial收集器采用复制算法,老年代是Serial Old收集器采用标记-整理算法。Serial虽然是一个缺点鲜明的收集器,但它依然是虚拟机在Client模式下的默认收集器,它也有优势,好比简单高效(与其余收集器单线程相比),对于单个CPU来讲,Serial因为没有线程交互的开销,效率比较高,对于桌面应用来讲,分配给虚拟机的内存不会很大,收集时的停顿也是在可接受范围内的。性能

ParNew 收集器

ParNew收集器是Serial收集器的多线程版本,也是使用复制算法的新生代收集器,它除了使用多条线程进行垃圾收集之外,其余的好比收集器的控制参数、收集算法、Stop-The-World、对象分配规则、回收策略都和Serial收集器彻底同样。线程

下图是 ParNew/Serial Old 收集器运行示意图:3d

image

上图中,新生代是ParNew收集器采用复制算法,老年代是Serial Old收集器采用标记-整理算法。ParNew是许多Server模式下虚拟机的首选新生代收集器,可能是由于它能与CMS收集器配合工做。CMS收集器是HotSpot虚拟机中第一个并发的垃圾收集器,CMS第一次实现了让用户线程与垃圾收集线程同时工做。日志

简单介绍下垃圾收集中的并行与并发概念:code

  • 并行(Parallel):指多条垃圾收集线程并行工做,但此时用户线程是等待状态。
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行,用户程序运行的同时,垃圾收集程序运行于另外一个CPU上。

Parallel Scavenge 收集器

Parallel Scavenge也是使用复制算法的新生代收集器,而且也是一个并行的多线程收集器。Parallel收集器跟其它收集器关注GC停顿时间不一样,它关注的是吞吐量。低停顿时间适合须要与用户交互的程序,而高吞吐量能够高效率的利用CPU时间,能尽快完成运算任务,适合用于后台计算较多而交互较少的任务。

  • 吞吐量(Throughput):CPU用于运行用户代码的时间与CPU总消耗时间的比值,吞吐量 = 运行用户代码时间 /(运行用户代码时间+垃圾收集时间)。

Parallel收集器提供了两个虚拟机参数用以控制吞吐量,-XX:MaxGCPauseMillis参数能够控制垃圾收集的最大停顿时间,-XX:GCTimeRatio参数能够直接设置吞吐量大小。

-XX:MaxGCPauseMillis的值是一个大于0的毫秒数,使用它减少GC停顿时间是牺牲吞吐量和新生代空间换来的,例如系统把新生代调小,收集300M的新生代确定比500M的快,这也致使垃圾收集发生的更频繁,原来10秒收集一次每次停顿100毫秒,如今5秒收集一次每次停顿70毫秒,停顿时间降低了,可是吞吐量也降低了。

-XX:GCTimeRatio的值是一个0到100的整数,经过它咱们告诉JVM吞吐量要达到的目标值,-XX:GCTimeRatio=N指定目标应用程序线程的执行时间(与总的程序执行时间)达到N/(N+1)的目标比值。例如,它的默认值是99,就是说要求应用程序线程在整个执行时间中至少99/100是活动的(GC线程占用其他的1/100),也就是说,应用程序线程应该运行至少99%的总执行时间。

除这两个参数外,还有一个参数-XX:-UseAdaptiveSizePolicy值得关注,这是一个开关参数,当它打开以后,就不须要手工指定新生代大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据系统的运行状况收集性能监控信息,动态的调整这些参数来提升GC性能,这种调节方式称为GC自适应调节策略。这个参数是默认激活的,自适应行为也是JVM优点之一。

Serial Old 收集器

Serial Old是Serial收集器的老年代版本,一样是一个“单线程”收集器,使用标记-整理算法。这个收集器主要是给Client模式下的虚拟机使用,Server模式下还有两个用途,一个是在JDK1.5及以前的版本中与Parallel Scavenge收集器搭配使用,另外一个是做为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。工做过程请看Serial 收集器部分的 Serial/Serial Old 收集器运行示意图。

Parallel Old 收集器

Parallel Old收集器是Parallel Scavenge的老年代版本,使用多线程标记-整理算法。此收集器在JDK1.6中开始出现,在Parallel Old出现以前,只有Serial Old可以与Parallel Scavenge收集器配合使用。因为Serial Old这种单线程收集器的性能拖累,致使在老年代比较大的场景下,Parallel Scavenge和Serial Old的组合吞吐量甚至还不如ParNew加CMS的组合。而有了Parallel Old收集器以后,Parallel Scavenge与Parallel Old成了名副其实的吞吐量优先的组合,在注重吞吐量和CPU资源敏感的场景下,均可以优先考虑这对组合。

下图是 ParNew/Serial Old 收集器运行示意图:

image

CMS 收集器

CMS(Concurrent Mark Sweep)收集器是基于标记-清除算法的老年代收集器,它以获取最短回收停顿时间为目标。CMS是一款优秀的收集器,特色是并发收集、低停顿,它的运行过程稍微复杂些,分为4个步骤:

  1. 初始标记(CMS initial mark)
  2. 并发标记(CMS concurrent mark)
  3. 从新标记(CMS remark)
  4. 并发清除(CMS concurrent sweep)

4个步骤中只有初始标记、从新标记这两步须要“Stop The World”。初始标记只是标记一下GC Roots能直接关联的对象,速度很快。并发标记是进行GC Roots Tracing的过程,也就是从GC Roots开始进行可达性分析。从新标记则是为了修正并发标记期间因用户线程继续运行而致使标记发生变更的那一部分记录。并发清理固然就是进行清理被标记对象的工做。

下图是 CMS 收集器运行示意图:

image

整个过程当中,并发标记与并发清除过程耗时最长,但它们均可以与用户线程一块儿工做,因此总体上说,CMS收集器的内存回收过程是与用户线程一块儿并发执行的。

可是CMS收集器也并不完美,它有如下3个缺点:

  1. CMS收集时对CPU资源很是敏感,并发阶段虽然不会致使用户线程停顿,可是会由于占用CPU资源致使应用程序变慢、总吞吐量变低。
  2. CMS收集器没法处理浮动垃圾(Floating Garbage),可能会产生Full GC。浮动垃圾就是在并发清理阶段,依然在运行的用户线程产生的垃圾。这部分垃圾出如今标记过程以后,CMS没法在当次集中处理它们,只能等下一次GC时清理。
  3. CMS是基于标记-清除算法的收集器,可能会产生大量的空间碎片,从而没法分配大对象而致使Full GC提早产生。

G1 收集器

G1(Garbage-First)收集器是面向服务端应用的垃圾收集器,它被寄予厚望以用来替换CMS收集器。在G1以前的收集器中,收集的范围要么是整个新生代要么就是老年代,而G1再也不从物理上区分新生代老年代,G1能够独立管理整个Java堆。它将Java堆划分为多个大小相等的独立区域(Region),虽然还有新生代老年代的概念,但再也不是物理隔离的,而都是一部分Region(不须要连续)的集合。

与其余收集器相比,G1收集器的特色有:

  1. 并行与并发:G1能充分利用多CPU或者多核心的CPU,来缩短Stop The World的停顿时间。
  2. 分代收集:虽然G1收集器能够独立管理整个GC堆,但它能采用不一样的方式处理“新对象”和“老对象”,以达到更好的收集效果。
  3. 空间整合:G1从总体看是基于标记-整理算法的,从局部看(两个Region之间)是基于复制算法实现的,这两个算法在收集时都不会产生空间碎片,这样就有连续可用的内存用以分配大对象。
  4. 可预测的停顿:G1除了追求低停顿外,还能创建可预测的停顿时间模型,能够明确指定一个最大停顿时间(-XX:MaxGCPauseMillis),停顿时间须要不断调优找到一个理想值,过大太小都会拖慢性能。

G1收集器之因此能创建可预测的停顿时间模型,是由于它能够避免在整个Java堆中进行全区域的垃圾收集,G1根据各个Region里垃圾堆积的价值大小(回收所获空间大小及所需时间的经验值),在后台维护一个优先列表,每次根据容许的收集时间,优先回收价值最大的Region,这也是Garbage-First名称的由来。

G1收集器的Region以下图所示:

image

图中的E表明是Eden区,S表明Survivor,O表明Old区,H表明humongous表示巨型对象(大于Region空间的对象)。从图中能够看出各个区域逻辑上并非连续的,而且一个Region在某一个时刻是Eden,在另外一个时刻就可能属于老年代。G1在进行垃圾清理的时候就是将一个Region的对象拷贝到另一个Region中。

再介绍一个概念:Remembered Set(记忆集)。每一个Region中都有一个Remembered Set,记录的是其余Region中的对象引用本Region对象的关系(谁引用了个人对象)。因此在垃圾回收时,在GC根节点的枚举范围中加入Remembered Set便可保证不对全堆扫描也不会有遗漏。G1里面还有另一种数据结构叫Collection Set,Collection Set记录的是GC要收集的Region的集合,Collection Set里的Region能够是任意代的。在GC的时候,对于跨代对象引用,只要扫描对应的Collection Set中的Remembered Set便可。

不算上维护Remembered Set的话,G1收集器的收集过程以下图所示:

image

如图所示,G1收集过程有以下几个阶段:

  1. 初始标记(Initial Marking)
  2. 并发标记(Concurrent Marking)
  3. 最终标记(Final Marking)
  4. 筛选回收(Live Data Counting and Evacuation)

初始标记只标记一下GC Roots能关联到的对象,须要停顿线程可是耗时短,会停顿用户线程(Stop the World)。并发标记是从GC Root开始对堆中对象进行可达性分析,找出存活对象,这阶段耗时长可是能够与用户线程并发执行。最终标记就是为了修正在并发标记阶段,因用户线程继续运行而致使标记产生变更的那一部分标记记录,这阶段须要停顿用户线程(Stop the World),可是可并行执行。筛选回收阶段会对各个Region的回收价值和成本进行排序,根据用户指望的GC停顿时间来制定回收计划,该阶段也是会停顿用户线程(Stop the World)。

垃圾收集参数

查询当前使用的垃圾收集器:
java -XX:+PrintCommandLineFlags -version
此命令让JVM打印出那些已经被用户或者JVM设置过的详细的XX参数的名称和值。

VM参数 描述
-XX:+UseSerialGC 指定Serial收集器+Serial Old收集器组合执行内存回收
-XX:+UseParNewGC 指定ParNew收集器+Serilal Old组合执行内存回收
-XX:+UseParallelGC 指定Parallel收集器+Serial Old收集器组合执行内存回收
-XX:+UseParallelOldGC 指定Parallel收集器+Parallel Old收集器组合执行内存回收
-XX:+UseConcMarkSweepGC 指定CMS收集器+ParNew收集器+Serial Old收集器组合执行内存回收。优先使用ParNew收集器+CMS收集器的组合,当出现ConcurrentMode Fail或者Promotion Failed时,则采用ParNew收集器+Serial Old收集器的组合
-XX:+UseG1GC 指定G1收集器并发、并行执行内存回收
-XX:+PrintGCDetails 打印GC详细信息
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式)
-XX:+PrintHeapAtGC 在进行GC的先后打印出堆的信息
-XX:+PrintTenuringDistribution 在进行GC时打印survivor中的对象年龄分布信息
-Xloggc:$CATALINA_HOME/logs/gc.log 指定输出路径收集日志到日志文件
-XX:NewRatio 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2
-XX:SurvivorRatio eden/survivor 空间大小的比例(Ratio). 默认值为 8
-XX:GCTimeRatio GC时间占总时间的比率,默认值99%,仅在Parallel Scavenge收集器时生效
-XX:MaxGCPauseMills 设置GC最大停顿时间,仅在Parallel Scavenge收集器时生效
-XX:PretensureSizeThreshold 直接晋升到老年代的对象大小,大于这个参数的对象直接在老年代分配
-XX:MaxTenuringThreshold 提高年老代的最大临界值(tenuring threshold). 默认值为 15
-XX:UseAdaptiveSizePolicy 动态调整Java堆中各个区域的大小及进入老年代的年龄
-XX:HandlePromotionFailure 是否容许分配担保失败,即老年代的剩余空间不足以应付新生代整个Eden和Survivor中对象都存活的极端状况
-XX:ParallelGCThreads 设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不一样而不一样
-XX:ParallelCMSThreads 设定CMS的线程数量
-XX:ConcGCThreads 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不一样而不一样
-XX:CMSInitiatingOccupancyFraction 设置CMS收集器在老年代空间被使用多少后触发垃圾收集,默认68%
-XX:+UseCMSCompactAtFullCollection 设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理
-XX:CMSFullGCsBeforeCompaction 设定进行多少次CMS垃圾回收后,进行一次内存压缩
-XX:+CMSClassUnloadingEnabled 容许对类元数据进行回收
-XX:CMSInitiatingPermOccupancyFraction 当永久区占用率达到这一百分比时,启动CMS回收
-XX:UseCMSInitiatingOccupancyOnly 表示只在到达阀值的时候,才进行CMS回收
-XX:InitiatingHeapOccupancyPercent 指定当整个堆使用率达到多少时,触发并发标记周期的执行,默认值是45%
-XX:G1HeapWastePercent 并发标记结束后,会知道有多少空间会被回收,再每次YGC和发生MixedGC以前,会检查垃圾占比是否达到此参数,达到了才会发生MixedGC
-XX:G1ReservePercent 设置堆内存保留为假天花板的总量,以下降提高失败的可能性. 默认值是 10
-XX:G1HeapRegionSize 使用G1时Java堆会被分为大小统一的的区(region)。此参数能够指定每一个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb
相关文章
相关标签/搜索