携手创作,共同成长!这是我参与「日新计划 8 月更文挑战」的第12天,点击查看活动详情


1.写在前面

在上一篇文章中,我们分享了jvm调优中:堆内存与元空间优化,新生代和老年代比例优化,和线程堆栈优化。

具体的详情可以查看:JVM调优-JVM调优实践二

通过了这个三个方面的内容,对jvm进行相应的调优实践,并得到最终的结论。

好了,那我们想象一下,我们还可以往哪个方向去调优呢?

嘿,我们之前花了比较大的时间分析了jvm的垃圾回收器,那我们是不是可以从jvm垃圾回收器方向进行调优呢?

答案是肯定的,那我们今天就继续,接下来的jvm调优实践吧!!!

那我们今天就继续往下进行分析:

  • 垃圾回收器优化(吞吐量,响应时间)

那就废话不多说了,直接上正菜吧:

JVM调优-JVM调优实践三

2.垃圾回收器优化:吞吐量优先ps+po

  • Perallel-Scavenge垃圾收集器:-XX:+UsePerallelGC
    • 吞吐量优先
    • 年轻代是并行回收的,老年代是串行回收的【STW】
    • 新生代收集器复制算法,老年代采用的是标记-整理算法【mark-compart】
    • 并行的多线程收集器
  • Perallel-Old垃圾收集器【po是ps的老年代并行收集版本】:-XX:+UsePerallelOldGC
    • 吞吐量优先
    • 老年代采用并行垃圾回收

默认使用 ps+po 垃圾回收器组合: 并行垃圾回收器组合

这里说的垃圾收集器的搭配,我们之前已经分享过了,估计大家伙都忘了吧,这里,我们再回顾一下:

JVM调优-JVM调优实践三

纳,就是上面这个喽:(答应我,别再忘了)

优化后的参数配置:

JAVA_OPT="${JAVA_OPT} -Xms424m -Xmx424m -XX:MetaspaceSize=128m -Xss512k"
JAVA_OPT="${JAVA_OPT} -XX:+UseParallelGC -XX:+UseParallelOldGC "
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-ps-po.log"

idea配置:

JVM调优-JVM调优实践三

-Xms424m -Xmx424m -Xss512k -XX:MetaspaceSize=128m -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-default.log

然后,重启一下项目。

这里,我们用jmeter,进行压力测试,看看平均响应时间:

JVM调优-JVM调优实践三

平均响应时间: 1904

这里,jmeter如何进行压力测试,大家伙可以往前看,我的文章有说明。

这里就不再展开描述了,直接给出测试结果了。

3.垃圾回收器优化:响应时间优先parnew+cms

  • ParNew垃圾收集器【Serial的收集器的多线程版本】:-XX:UseParNewGC
    • 年轻代是并行的垃圾回收,老年代是串行垃圾回收
    • 注意:单核的CPU性能并不如Serial,没有线程切换成本
  • CMS垃圾收集器: 并发 收集器(非独占式)-XX:ConcMarkSweepGC【并发的标记清楚算法的GC垃圾收集器】
    • 响应优先(低延时)
    • 可以执行并发收集的垃圾收集器
    • 垃圾收集采用的是标记清除算法
    • 缺点:容易产生内存碎片,对CPU比较敏感
    • 主要分为四个阶段:
      1. 初始标记(Initial-Mark)阶段 :【STW】
      2. 并发标记(Concurrent-Mark)阶段
      3. 重新标记(Remark)阶段 :【STW】
      4. 并发清除(Concurrent-Sweep)阶段

使用cms垃圾回收器,垃圾回收器组合: parNew+CMS, cms垃圾回收器在垃圾标记,垃圾清除的时候,和业务线程交叉执行,尽量减少stw时间,因此这垃圾回收器叫做响应时间优先

JVM调优-JVM调优实践三

纳,就是上面这个喽:(答应我,别再忘了)

优化后的参数配置:

JAVA_OPT="${JAVA_OPT} -Xms424m -Xmx424m -XX:MetaspaceSize=128m -Xss512k"
JAVA_OPT="${JAVA_OPT} -XX:+UseParNewGC -XX:+UseConcMarkSweepGC "
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-parnew-cms.log"

idea配置:

JVM调优-JVM调优实践三

-Xms424m -Xmx424m -Xss512k -XX:MetaspaceSize=128m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-default.log

然后,重启一下项目。

这里,我们用jmeter,进行压力测试,看看平均响应时间:

JVM调优-JVM调优实践三

平均响应时间: 1893

由上可见,这两种方式,平均响应时间,其实,相差不大。

这里,jmeter如何进行压力测试,大家伙可以往前看,我的文章有说明。

这里就不再展开描述了,直接给出测试结果了。

4.G1垃圾收集器(重点)

Garbage First(简称G1) 收集器是垃圾收集器技术发展历史上的里程碑式的成果

JDK 8以后G1收集器才被Oracle官方称为“全功能的垃圾收集器”(Fully-Featured Garbage Collector)。

G1是一款主要面向服务端应用的垃圾收集器。

JDK 9发布之日,G1宣告取代Parallel Scavenge加Parallel Old组合,成为服务端模式下的默认垃圾收集器,而CMS则沦落至被声明为不推荐使用(Deprecate)的收集器。

由此可见,G1变得越来越重要了。CMS变成了中间过度。

G1中提供了三种模式垃圾回收模式, Young GC、Mixed GC 和 Full GC ,在不同的条件下被触发。

G1的使用步骤:

  • 配置开启G1
  • 设置对的最大内存
  • 设置GC最大暂停时间:设置100-300之间是比较合理:
    • 如果设置的GC的暂停时间比较小,系统吞吐量会降低
    • 默认的值是200,
    • G1设计的目标:控制用户线程执行时间90%,GC占比时间10%

4.1原理

G1中没有之前所谓老年代,新生代,Eden、S0、S1!!!!

JVM调优-JVM调优实践三

G1垃圾收集器相对比其他收集器而言,最大的区别在于它取消了 年轻代、老年代的物理划分, 取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的年轻代、老年代区域。

这样做的好处就是,我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。

这个确实是一个大很大的优点。

在G1划分的区域中,年轻代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。

这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。

4.2 Young GC

Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。

Remembered Set(记忆集合)

  1. 如何找到垃圾?引用计数法&根可达算法GC Roots

基于根可达算法回收垃圾对象,判断对象是否是GC Roots,如果全量扫描老年代,那么这样扫描下来会耗费大量的时间。

于是,G1引进了RSet的概念。它的全称是Remembered Set,其作用是用来记录并跟踪其它Region指向该Region中对象的引用。

RSet 与 Card 的关系

每个Region初始化时,会初始化一个RSet,该集合用来记录并跟踪其它Region指向该Region中对象的引用,每个Region默认按照512Kb划分成多个Card,所以RSet需要记录的东西应该是 xx Region的 xx Card。

4.3 Mixed GC

特点: 回收年轻代内存,同时也回收部分老年代的内存

当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分的Old Region

注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC 并不是 Full GC。

MixedGC什么时候触发?

-XX:InitiatingHeapOccupancyPercent=n ,设置触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%。

MixedGC主要分为两步:

  1. 全局并发标记
  2. 拷贝存活对象

好了,相关的原理,描述得比较多,大家耐心点看喽。

接下来,就开启配置。

4.4 G1收集器相关参数

# 使用 G1 垃圾收集器
-XX:+UseG1GC
# 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值是 200 毫秒。
-XX:MaxGCPauseMillis=
# 设置的 G1 区域的大小。值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划分出约 2048 个区域。
# 默认是堆内存的1/2000。
-XX:G1HeapRegionSize=n
# 设置 STW 工作线程数的值。将 n 的值设置为逻辑处理器的数量。n 的值与逻辑处理器的数量相同,最多为 8。
-XX:ParallelGCThreads=n
# 设置并行标记的线程数。将 n 设置为并行垃圾回收线程数 (ParallelGCThreads)的 1/4 左右。
-XX:ConcGCThreads=n
# 设置触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%。
-XX:InitiatingHeapOccupancyPercent=n

4.5 测试

JAVA_OPT="${JAVA_OPT} -Xms424m -Xmx424m -XX:MetaspaceSize=128m -Xss512k"
JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:MaxGCPauseMillis=100"
# MaxGCPauseMillis设置的值建议是100-300之间
JAVA_OPT="${JAVA_OPT} -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-g-one.log"

idea配置:

JVM调优-JVM调优实践三

-Xms424m -Xmx424m -Xss512k -XX:MetaspaceSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E:/logs/gc-default.log

然后,重启一下项目。

看看效果:

  • GC统计:

    JVM调优-JVM调优实践三

  • gc原因

JVM调优-JVM调优实践三

由上可见,可以看到gc的次数,明显减少。暂停时间也有减少。


由此可见,我们这里,已经是对垃圾回收器优化,进行了相关的描述了。

那到此,jvm的调优就到此告一段落了。多谢大家的点赞,评论,收藏,三连!!!


好了,以上就是JVM调优实践三的分享了。

个人理解,可能也不够全面,班门弄斧了。

如果觉得有收获的,帮忙点赞、评论、收藏一下呗!!!

JVM调优-JVM调优实践三