Java-Jvm-04-参数调优

1. 何为Jvm调优

目标: 使用较小的内存占用来获取较高的吞吐量或者较低的延迟

三个指标:

  • 内存占用: 程序正常运行需要的内存大小
  • 延迟: 由于垃圾回收器而引起的程序停顿时间
  • 吞吐量: 用户程序运行的时间占用用户程序和垃圾收集占用总时间占比

垃圾回收GC分为两种:

  • MinorGC(YGC): 年轻代
  • Major(FullGc): 老年代

从JVM调优的角度看。我们应该尽量避免发生YGC和FullGC,或者使其时间更短

可以之间复制大佬的调优:李假笨大神的调优网站

根据GC日志分析:https://gceasy.io/

2. 相关参数解释

  • 查看常见的默认参数:
    java -XX:+PrintCommandLineFlags -version

  • 查看默认初始化参数:
    java -XX:+PrintFlagsInitial

  • 查看最终参数:
    java -XX:+PrintFlagsFinal -version

常见参数如下:

  • -Xms: 初始堆大小内存默认为物理内存的1/64

  • -Xmx: 最大分配内存默认为物理内存的1/4

  • -Xss: 单个线程栈的大小

  • -Xmn: 年轻代的大小

  • -XX: MetaspaceSize: 元空间大小,默认20.79M

  • -XX: SurvivorRatio=8 设置新生代中eden与so/s1的比例,默认8:1:1

  • -XX: NewRatio=2 设置老年代与年轻代在堆中的占比,默认2:1

  • -XX:MaxTenuringThreshold=15 年轻代晋升老年代的年龄

  • -XX:ParallelGCThreads=4 配置垃圾收集器的线程数,即多少线程一起进行垃圾回收,建议配置和系统CPU数相同

  • -XX:MaxGCPauseMillis=18446744073709551615 设置每次年轻代垃圾回收的最长时间,如果无法满足该时间,jvm会自动调整年轻代大小满足该时间

  • -XX:PrintPromotionFailure=false 如果有新生代晋升到老年代失败出现FullGC,打开这个可以看到更详细的信息

  • -XX: HandlerPromotionFailure=true 是否开启空间分担担保策略

  • -XX:UseCMSCompactAtFullCollectio=true

  • -XX:CMSFullGCsBeforeCompaction=0 : (有一点需要注意的是:CMS并发GC不是full GC。HotSpot VM里对concurrent collection和full collection有明确的区分。所有带有“FullCollection”字样的VM参数都是跟真正的full GC相关,而跟CMS并发GC无关的。)

CMS GC要决定是否在full GC时做压缩,会依赖几个条件:

  • 第一种条件,UseCMSCompactAtFullCollection 与 CMSFullGCsBeforeCompaction 是搭配使用的;前者目前默认就是true了,也就是关键在后者上。
  • 第二种条件是用户调用了System.gc(),而且DisableExplicitGC没有开启。
  • 第三种条件是young gen报告接下来如果做增量收集会失败;简单来说也就是young gen预计old gen没有足够空间来容纳下次young GC晋升的对象。

上述三种条件的任意一种成立都会让CMS决定这次做full GC时要做压缩。
CMSFullGCsBeforeCompaction 说的是,在上一次CMS并发GC执行过后,到底还要再执行多少次full GC才会做压缩。默认是0,也就是在默认配置下每次CMS GC顶不住了而要转入full GC的时候都会做压缩。 把CMSFullGCsBeforeCompaction配置为10,就会让上面说的第一个条件变成每隔10次真正的full GC才做一次压缩(而不是每10次CMS并发GC就做一次压缩,目前VM里没有这样的参数)。这会使full GC更少做压缩,也就更容易使CMS的old gen受碎片化问题的困扰。 本来这个参数就是用来配置降低full GC压缩的频率,以减少某些full GC的暂停时间。CMS回退到full GC时用的算法是mark-sweep-compact,但compaction是可选的,不做的话碎片化会严重些但这次full GC的暂停时间会短些;这是个取舍。

  • -XX:CMSInitiatingOccupancyFraction=-1 表示默认情况下当老年代的空间超过百分之92的时候会触发CMS GC,一般会将该值设置小一些,防止晋级失败等问题
    该参数需要配置UseCMSInitiatingOccupancyOnly一起使用
    所以常用组合为:

  • -XX: +UseCMSInitiatngOccupancyOnly -XX: CMSInitiatingOccupancyfaction=80

  • -XX: +PrintGCDetails : 默认是关闭false 可以配置打开,搭配下面参数使用
    -Xloggc: /data/jvm/gc.log 或者 -verbose:gc 打印在控制台

标配参数:

  • x参数:
    x参数(了解): -Xint 解释执行 -Xcomp第一次使用就编译成本地代码 -Xmixed混合模式
  • xx参数:
    xx参数主要分为: boolean类型 -xx:+或者-n某个属性
    +代表开启 -代表关闭
  • 标配参数: 初始配置参数 -version -help

3. 内存分配策略

在这里插入图片描述

3.1 对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC,但也有一种情况,在内存担保机制下,无法安置的对象会直接进到老年代。

3.2 大对象直接进入老年代

大对象时指需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。
虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。目的就是避免在Eden区及两个Survivor区之间发生大量的内存复制。但是这个参数默认是0意味着任何对象都会现在新生代分配内存

[root@localhost ~]# java -XX:+PrintFlagsFinal  | grep PretenureSizeThreshold
   size_t PretenureSizeThreshold   = 0    {product} {default}

当该值设定了大小,eg: -XX:PretenureSizeThreshold=100000000
如果该对象的大小超过这个数,则直接放入老年区

3.3 长期存活的对象将进入老年代

虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1 。对象在Survivor区中没经过一次Minor GC,年龄就加1岁,当年龄达到15岁(默认值),就会被晋升到老年代中。

对象晋升老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold设置。

[root@localhost ~]# java -XX:+PrintFlagsFinal  | grep MaxTenuringThreshold
 uintx MaxTenuringThreshold  = 15   {product} {default}

年龄为什么是15?

先看下这篇文章关于对象头:对象头
HotSpot虚拟机的对象头其中一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称它为“Mark word”。

例如,在32位的HotSpot虚拟机中,如果对象处于未被锁定的状态下,那么Mark Word的32bit空间中25bit用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0 。
对象的分代年龄占4位,也就是0000,最大值为1111也就是最大为15;

3.4 动态对象年龄判定

虚拟机晋升老年代要求:

  • 对象的年龄达到了MaxTenuringThreshold(默认15)能晋升老年代;
  • 在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
[root@localhost ~]# java -XX:+PrintFlagsFinal  | grep TargetSurvivorRatio
 uintx TargetSurvivorRatio  = 50   {product} {default}

这个参数TargetSurvivorRatio,目标存活率,默认为50%,当survivor区目标使用率,各个年龄段的总占用空间之和超过百分之50,JVM会动态的计算tenuring threshold的值。一旦对象年龄达到了tenuring threshold就会晋升到老年代。从该年龄段开始及大于的年龄对象就要进入老年代;
  为什么要动态的计算tenuring threshold的值呢?假设有很多年龄还未达到TenuringThreshold的对象依旧停留在survivor区,这样不利于新对象从eden晋升到survivor。因此设置survivor区的目标使用率,当使用率达到时重新调整TenuringThreshold值,让对象尽早的去old区。
  如果希望跟踪每次新生代GC后,survivor区中对象的年龄分布,可在启动参数上增加-XX:+PrintTenuringDistribution

3.5 空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC ;

4. GC触发条件

4.1 MinorGC触发条件

虚拟机在进行minorGC之前会判断老年代最大的可用连续空间是否大于新生代的所有对象总空间

  • 如果大于的话,直接执行minorGC

  • 如果小于,判断是否开启HandlerPromotionFailure,没有开启直接FullGC

  • 如果开启了HanlerPromotionFailure, JVM会判断老年代的最大连续内存空间是否大于历次晋升(晋级老年代对象的平均大小)平均值的大小,如果小于直接执行FullGC
    如果大于的话,执行minorGC

对于HandlerPromotionFailure,我们可以这样理解,在发生Minor GC之前,虚拟机会先检查老年代的最大的连续内存空间是否大于新生代的所有对象的空间,如果这个条件成立,Minor GC是安全的。如果不成立虚拟机会查看HanlerPromotionFailure 设置值是否允许担当失败,如果允许,那么会继续检查老年代最大可用的连续内存空间是否大于历次晋级到老年代对象的平均大小,如果大于就尝试一次Minor GC, 如果小于,或者HanlerPromotionFailure 不愿承担风险就要进行一次Full GC 。

通俗理解:

上面的担保就好比你去买房,要去银行贷款。如果你的贷款金额比较大,那么一般银行会需要你提供担保人。说白了,银行就是担心你每个月的工资不够还贷款。这里的老年代就相当于你的担保人。如果你的担保人的每个月的收入都不够你的月供。那银行肯定不会贷款给你的。在jvm中就是肯定会直接执行Full GC了。(强行比喻一番,能理解就好。。)

4.2 FullGC触发条件

  • 老年代空间不足
    如果创建一个大对象,Eden区域当中放不下这个大对象,会直接保存在老年代当中,如果老年代空间也不足,就会触发Full GC。为了避免这种情况,最好就是不要创建太大的对象。

  • 持久代空间不足
    如果有持久代空间的话,系统当中需要加载的类,调用的方法很多,同时持久代当中没有足够的空间,就出触发一次Full GC

  • YGC出现promotion failure
    promotion failure发生在Young GC, 如果Survivor区当中存活对象的年龄达到了设定值,会就将Survivor区当中的对象拷贝到老年代,如果老年代的空间不足,就会发生promotion failure, 接下去就会发生Full GC.

  • 统计YGC发生时晋升到老年代的平均总大小大于老年代的空闲空间
    在发生YGC是会判断,是否安全,这里的安全指的是,当前老年代空间可以容纳YGC晋升的对象的平均大小,如果不安全,就不会执行YGC,转而执行FullGC。

  • 显示调用System.gc
    这里调用了 System.gc ,会调用finalize(),但是只是建议回收机制回收,可能不会立即回收,也就不一定会立马就触发FullGC
    建议使用Runtime.getRuntime().gc(),因为 System.gc 也是调用Runtime.getRuntime().gc() 一回事;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alan0517

感谢您的鼓励与支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值