JVM GC机制初探(三)

前言

1. GC处理重点区域

2. STW机制

3. 对象分配规则

一、垃圾标记

1. 引用计数算法

2. 根搜索算法

二、垃圾收集器算法的分类

1. 标记清除算法(Mark-Sweep)

2. 复制算法(Copying)

3. 标记压缩算法(Mark-Compact)

4. 增量算法(Incremental Collecting)

5. 分代算法(Generational Collecting)

三、常见的垃圾收集器

四、如何监控GC

1. 通过工具可视化分析

2. 通过日志文件分析

五、JVM总结

1. 收集器性能指标

2. 收集器的分类

3. 触发GC的场景

4. GC调优策略

5. JVM总架构图


前言

    在对垃圾回收之前先简述那些GC处理的重点区域、STW机制、对象分配原则。

1. GC处理重点区域

   既然要进行GC那么针对JVM的内存而言,有那些内存模块是GC的重点区域呢?

    虚拟机栈、本地栈、PC寄存器这三个模块都是线程独享的,它们的生命周期与线程一致,当线程结束其便会自行回收。

    方法区或元空间区(其实在HotSpot中这两者指的一个东西)也会发生垃圾回收,不过这两块对于JVM运行效率影响不是那么大,故也不是GC关注的重点。因此GC关注的重点便是堆中的内存了。

2. STW机制

   全称Stop The World,指的是GC事件处理过程中停止所有应用程序线程的执行,简单地说就是在GC时,所有应用都处于不可用状态。要记住,目前无论采用那种GC算法都必定会出现STW情况,因此,对于GC的处理,需要根据业务考量选用那种算法。

为什么在进行GC过程一定要停下所有线程呢?

  • 1. 为了保证垃圾回收过程不会有新的垃圾产生;
  • 2. 解决在回收的过程中不致于将原先已被标记成非存活对象,后期被新线程重新引用的情况,这种情况产生就可能会造成将实际要使用的对象清理掉,最终可能导致虚拟机奔溃。

3. 对象分配规则

  • 优先分配在Eden区;
  • 大对象直接进入老年代。目的是减少Eden与Survivor区间大量内存拷贝;
  • 长期存活的对象根据设置的阈值晋升至老年代。
  • 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
  • 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。

一、垃圾标记

如何标记出待回收对象呢?

通常比较常见的垃圾标记算法有两种分别是引用计数法与根搜索法。

1. 引用计数算法

     为每个对象单独开辟一个计数器,当一个对象被引有时计数器则加1,否则减1,当没有引用则计数为0。

 优点:不用等到内存不够用再进行GC,因为可以在对计数器赋值时发现计数为0则可清除。

 缺点:致命的缺点就是无法解决循环引用对象的回收问题。

           也是因为这一致命缺点导致在java垃 圾回收器中没有直接使用该类算法。

           还有一些小缺点,需要额外开辟空间给引用计数器,以及赋值操作增加了额外的开销。

2. 根搜索算法

    以根对象集合为起始点搜索被根对象集合所连接的目标对象是否直接或间接可达。

  • 注:通过根搜索算法标记一个对象至少需要经过两次被标记为不可达才会标记为垃圾对象。
  • 根对象集合其实就是指对象或方法可能会引用对象的集合,如Java栈内的对象引用、本地方法栈内的对象引用、运行时常量池中的对象引用、方法区的静态属性的对象引用。

优点:解决标记算法中循环对象会导致的内存泄露问题,因此大部分的JVM都是使用该算法来进行垃圾标记的。

上述两种算法都是用于判断对象是否可以被回收,在Java中对象被细分为如下四种


二、垃圾收集器的分类

   垃圾标记后那么自然就是需要回收了,针对垃圾的回收通常有以下几种回收算法:标记清除算法、复制算法、标记压缩算法。像增量算法、分代收集算法其实可以说是通过前面三种算法衍变而来的。

1. 标记清除算法(Mark-Sweep)

算法概述:算法分标记与清除两阶段。该算法1960年被提出并成功应用于Lisp语言中。可见其诞生之日比Java历史还久远的多了。注:1996年1月23日,Sun发布了JDK1.0。这里应用的标记算法通常是根搜索标记算法

优点:不需要进行对象移动,只需要清除不存活的对象,故在存活对象比较多的内存中比较适用。

缺点:因为回收后的空间不连续,其最大的缺点是回收后会造成空间碎片。从而导致后续没有足够连续的可用空间给大对象分配,例如需要连续空间的数组。在对象的分配过程中不连续的内存空间工作效率会比较低。

适合场景:存活对象比较多的内存中比较适用。例如老年代区域。

 

2. 复制算法(Copying)

算法概述:首先将空间分成大小一样的两块,每次只使用一块,在垃圾回收时将存活的对象复制到空的那一块,然后对原先那一块中所有对象进行清除。

优点:解决标记清除算法内存碎片问题。只需要复制数据(修改堆顶指针),故其速度可以说是相当快的。

缺点:思想上可以说是以空间换取时间吧,因为每一次只有一块空间可以使用。

适合场景:存活对象比较少的内存中比较适用。比如年轻代很多对象都是朝生夕死,这样在年轻代使用该算法则需要移动的对象就会比较少了。因此该算法被广泛应用于JVM的年轻代中。这也正是JVM堆中年轻代有两个大小一样的Survivor的原因了。

 

3. 标记压缩算法(Mark-Compact)

算法概述:也称为标记整理算法,结合了标记清除算法对内存占用率的优点及复制算法在执行效率上的优点。该算法分为两个阶段,第一阶段采用标记清除算法中的标记算法,第二阶段采用复制算法的移动存活对象到空闲空间。

优点:没有内存碎片问题,执行效率高。

适合场景:标记-压缩算法解决了标记-清除算法效率低和容易产生大量内存碎片的问题,以及复制算法内存利用率低的问题,因此它被广泛应用于老年代中。

上述三种算法小结如下图所示:

   前文也说过,标记压缩算法是一种很理想的算法,因此目前大多数算法基本也是基于其改造而来。例如,为了减少STW下用户较长时间等待的情况。因而致使实时垃圾收集算法即增量算法的诞生。

 

4. 增量算法(Incremental Collecting)

算法概述:其实这一算法在实现上并没有引入新的东西,增量算法的思想是把原先需要一次性将所有的垃圾进行清理改成通过不断切换应用线程与GC线程交换执行。个人感觉有点类似并发操作,由于CPU的轮询时间片比较短,从而感觉不到GC STW的存在。

优点:减少系统停顿时间。

缺点:频繁切换线程可能会使垃圾回收总体成本上升,造成系统吞吐量的下降。

适合场景:关注响应速度,不太关注吞吐率的系统。

 

5. 分代算法(Generational Collecting)

算法思想来源:该算法是基于统计学而发展来的,通过统计学统计结果发现大多数的对象内存块的生命周期都比较短。还记得语音识别技术的质变拐点也正是由于引入了统计学分析,此处容我感慨下统计学的魅力真是无处不在啊。

算法概述:个人感觉这一算法在实现上也跟增量算法一样并没有引入新实现方法。但是其思想上却是一大跨越式的进步。核心思想,根据对象的存活情况,将对象分成不同的代,进而在不同代中通过合适的算法来进行GC。

优点:可以结合实际需要将各种GC算法组合起来运用到合适的内存块中。

适合场景:分代收集算法是目前大部分JVM的垃圾收集器采用的算法。

小结:

常说的垃圾回收算法与垃圾回收器是什么关系呢?

   通俗地说垃圾回收算法是一种思想,而垃圾回收器是应用回收算法思想实现而成的产品。


三、常见的垃圾收集器

以下为服务端垃圾回收器

小结:

Serial:适用于年轻代,采用复制算法、串行回收、STW机制的方式内存回收。适用于单CPU以及机器性能低下或内存小的环境,原因是单线程在性能低下环境少了频繁切换线程的性能开销(机器性能低下时线程切换会相对显的笨拙些)、内存小回收快;HotSpot中的client模式下年轻代的默认垃圾收集器。

ParNew:其实基本可以说是Serial并行版,同样是采用复制算法、串行回收、STW机制的方式内存回收。适用于多CPU、多核心环境下。

Paralle:并行收集器,可通过参数设置吞吐量大小阈值从而使JVM自动调整相应的参数而保证吞吐量,因此也被称为吞吐量优先的垃圾回收器。但要知道吞吐量与低延迟这是两个相互牵制的关系。也就是说吞吐量提上去后可能延迟性反而受到负面影响。

CMS: 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。

  CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。

如今JDK1.9默认采用G1方式为JVM的GC回收算法,那么其优势是什么呢?

   可以说G1结合了前面几种算法的优点,又避免了其缺点。总的来说G1的一个最大的贡献是它可以让我们设置最大停顿时间,只要设置了这个时间,G1就会通过自动调整年轻代空间大小和整体Java堆空间大小来匹配这个目标停顿时间。比如你设置了一个很短的停顿时间,G1会设置比较小的年轻代、比较大的整个Java堆空间,对应的老年代也会比较大。


四、如何监控GC

1. 通过工具可视化分析

   通常需要通过GC工具来进行监控,因为直接看日志有时可能是不够明了,目前市面上有不少现成可供可视化的GC监控工具,如 GCeasyGCview等。

gcviewer 工具的简单使用

  • 下载(笔者电脑是win 10故直接下载对应的jar包在win中运行)
  • gcviewer-1.36-SNAPSHOT.jar 官网下载地址 https://sourceforge.net/projects/gcviewer/
  • 获取gc日志并通过gcviewer工具运行,通过gcviewer工具进行GC日志分析,命令如下
D:\Users\test>java -jar gcviewer-1.36-SNAPSHOT.jar gc.log.7.current
  • 查看运行结果,运行后出弹出如下所示的界面

 

 

2. 获取gc日志并通过gcviewer工具运行

 

通过gcviewer工具进行GC日志分析,命令如下

 

 

如下通过将GC日志压缩后上传至GCeasy可得到如下图。

 

2. 通过日志文件分析

  • GC日志是一个很重要的工具,可通过设置不同的打印参数得到不同的打印日志从而对GC策略进行优化。如下:
[GC 246656K->243120K(376320K), 0.0929090 secs]
[Full GC 243120K->241951K(629760K), 1.5589690 secs]
  • 每行开始首先是GC的类型(可以是“GC”或者“Full GC”),然后是在GC之前和GC之后已使用的堆空间,再然后是当前的堆容量,最后是GC持续的时间(以秒计)。

打印更详细的输出日志:

[GC
    [PSYoungGen: 142816K->10752K(142848K)] 246648K->243136K(375296K), 0.0935090 secs
]
[Times: user=0.55 sys=0.10, real=0.09 secs]
  • 解说:

  这是一次在young generation中的GC,它将已使用的堆空间从246648K减少到了243136K,用时0.0935090秒。

此外我们还可以得到更多的信息:所使用的垃圾收集器(即PSYoungGen)、young generation的大小和使用情况(在这个例子中“PSYoungGen”垃圾收集器将young generation所使用的堆空间从142816K减少到10752K)。


五、JVM总结

1. 收集器性能指标

吞吐量: 这里的吞吐量是指应用程序所花费的时间和系统总运行时间的比值。我们可以按照这个公式来计算GC的吞吐量:系统总运行时间=应用程序耗时+GC耗时。如果系统运行了100分钟, GC耗时1分钟,则系统吞吐量为99%,GC的吞吐量一般不能低于95%。

 

2. 收集器的分类

  新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。

 

3. 触发GC的场景

对于分代GC来说通常有两种GC,Minor GC与Full GC。

注:

  • 1. Major GC/Full GC 整个堆内存的回收,Major GC通常是跟Full GC是等价的,因为收集Old Gen时一般会触发 Minor GC,所以相当于Full GC。
  • 2. 在JDK1.8环境下,默认使用的是Parallel Scavenge 。 jdk1.9 默认垃圾收集器G1。

笔者环境:

C:\Users\Administrator>java -XX:+PrintCommandLineFlags -version
...
-XX:+UseParallelGC
java version "1.8.0_201"
Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)
  • 3. 通常情况,JVM是默认垃圾回收优化的,在没有性能衡量标准的前提下,尽量避免修改GC的一些性能配置参数。如果一定要改,那就必须基于大量的测试结果或线上的具体性能来进行调整。

 

4. GC调优策略

5. JVM总架构图

 


参考:

本文仅为笔者个人学习总结,方便回顾,上述内容如有雷同侵权还请告知删除。

周明耀《深入理解JVM & G1 GC》

JVM的垃圾回收机制 总结(垃圾收集、回收算法、垃圾回收器)

一张图看懂JVM

垃圾回收算法与垃圾回收器

JVM实用参数(八)GC日志

22 | 如何优化垃圾回收机制?(付费课程)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值