【JVM · 调优】常用参数 & 垃圾回收

一. JVM运行时参数

1. JVM参数选项类型

3.1 类型一:标准参数选项

特点

  • 比较稳定,后续版本基本不会变化
  • -开头

各种选项

运行javajava -help可以看到所有的标准选项:

【JVM · 调优】常用参数 & 垃圾回收_第1张图片

补充内容:-server与-client

HotSpot JVM 有 两种模式,分布式server和client,分别通过-server-client模式设置。

  1. 在32位Windows系统上,默认使用Client类型的JVM。要想使用Server模式,则机器配置至少有2个以上的CPU和2G以上的物理内存。Client模式适用于堆内存要求较小的桌面应用程序,默认使用Serial串行垃圾收集器。
  2. 64位机器上只支持Server模式的JVM,适用于需要大内存的应用程序,默认使用并行垃圾收集器。

3.2 类型二:-X参数选项

特点

  • 非标准化参数
  • 功能比较稳定,官方说后续版本可能会变更
  • -X开头

各种选项

运行java -X命令可以看到所有的X选项

【JVM · 调优】常用参数 & 垃圾回收_第2张图片

JVM的JIT编译模式相关的选项

  • -Xint:禁用JIT,所有字节码都被解释执行,这个模式的速度最慢
  • -Xcomp:所有字节码第一次使用就都被编译成本地代码,然后再执行
  • -Xmixed:混合模式 (默认模式),让JIT根据程序运行情况,有选择地将某些代码编译成本地代码,然后再执行

特别地

-Xmx-Xms-Xss属于XX参数?

  • -Xms:设置初始Java堆大小,等价于-XX:InitialHeapsize
  • -Xmx:设置最大Java堆大小,等价于-XX:MaxHeapSize
  • -Xss:设置Java线程堆栈大小,等价于-ThreadStackSize

3.3 类型三:-XX参数选项

特点

  • 非标准化参数
  • 使用最多的参数类型
  • 实验性参数,不稳定
  • -XX开头

作用

  • 用于开发和调试JVM

分类

  • Boolean类型格式

    • -XX:+:表示启用 option 属性

    • -XX:-:表示进用 option 属性

    • 举例:

      -XX:+UseParallelGC	# 选择垃圾收集器为并行收集器
      -XX:+UseG1GC		# 表示启用G1收集器
      -XX:+UseAdaptveSizePolicy	# 自动选择年轻代区大小和相应Survivor区比例
      
    • 说明:因为有的指令默认开启,可以使用-关闭

  • 非Boolean类型格式(key-value类型)

    • 子类型1:数值型格式:-XX:

      • number表示数值,number可以带上单位,比如:mM表示MB,kK表示KB,gG表示GB(如:32k等同于32768)

      • 例如:

        -XX:NewSize=1024m			# 表示设置新生代初始大小为1024MB
        -XX:MaxGCPauseMillis=500	# 表示设置GC停顿时间:500毫秒
        -XX:GCTimeRatio=19			# 表示设置吞吐量
        -XX:NewRatio=2		# 表示新生代与老年代比例
        
    • 子类型2:非数值型格式:-XX:=

      • 例如:

        -XX:HeapDumpPath=/usr/local/heapdump.hprof	# 指定堆dump文件的存储路径
        

特点地

  • -XX:+PrintFlagsFinal
    • 输出所有参数的名称和默认值
    • 默认不包括DiagnosticExperimental的参数
    • 可以配合-XX:+UnlockDiagnosticVMOPtion-XX:UnlockExperimentalVMOptions使用

2. 添加JVM参数选项

2.1 Eclipse

代码处右键:

【JVM · 调优】常用参数 & 垃圾回收_第3张图片

设置虚拟机参数:

【JVM · 调优】常用参数 & 垃圾回收_第4张图片

2.2 IDEA

编辑启动项:
【JVM · 调优】常用参数 & 垃圾回收_第5张图片

设置虚拟机参数:

【JVM · 调优】常用参数 & 垃圾回收_第6张图片

2.3 运行jar包

运行jar包时,可以在-jar指令前添加相关参数:

java -Xms50m -Xmx50m -XX:+PrintGCTimeStamps -jar xxx.jar

2.4 通过Tomcat运行war包

**Linux系统:**可在 tomcat/bin/catalina.sh 中添加类似如下配置:JAVA_OPTS="-Xms512M -Xmx1024M"

**Windows系统:**在catalina.bat中添加如下配置:JAVA_OPTS="-Xms512M -Xmx1024M"

2.5 程序运行过程中

非Boolean类型参数: 使用jinfo -flag=

Boolean类型参数: 使用jinfo -flag[+|-]

【注】在程序运行过程中,部分参数不允许修改(如: GC, 堆…)

3. 常用的JVM参数选项

3.1 打印设置的XX选项及值

  • -XX:+PrintCommandLineFlags:可以让在程序运行前打印出用户手动设置或者JVM自动设置的-xx选项
  • -XX:+PrintFlagsInitial:表示打印出所有XX选项的默认值
  • -XX:+PrintFlagFinal:表示打印出XX选项在运行程序时生效的值(:=代表此值非默认值,=代表此值为默认值)
  • -XX:+PrintVMOption:打印JVM的参数

3.2 堆、栈、方法区等内存大小设置

3.2.1 栈
  • -Xss128k:设置每个线程的栈大小为128KB
    • 等价于-XX:ThreadStackSize=128k
3.2.2 堆内存
  • -Xms3550m:设置JVM初始堆内存为3550MB
    • 等价于-XX:InitialHeapSize=3550m
  • -Xmx3550m:设置JVM最大堆内存为3550MB
    • 等价于-XX:MaxHeapSize=3550m
  • -Xmn2g:设置年轻代大小为2g
    • 官方推荐配置大小为整个堆大小的3/8
  • -XX:NewSize=1024m:设置年轻代初始值为1024m
  • -XXMaxNewSize=1024m:设置年轻代最大值为1024m
  • -XX:SurvivorRatio=8:设置年轻代Eden区与一个Survivor区的比例(默认为8, 即 8:1:1
    • 参数UseAdaptiveSizePolicy默认开启,优先于SurvivorRatio,可能比例不为8。
    • 禁用UseAdaptiveSizePolicy后,还需手动配置XX:SurvivorRatio方可生效。
  • -XX:+UseAdaptiveSizePolicy:自动选择各区大小比例(默认开启)
  • -XX:NewRatio=4:设置老年代与年轻代(包括1个Eden与2个Survivor区)的比值
    • 默认新生代占1/3,老年代占2/3
    • 其中Eden区默认占新生代的8/10,Survivor0 / Survivor1 各占新生代的1/10
  • -XX:PetenureSizeThreadshold=1024:设置让大于此阈值的对象直接分配在老年代(单位: 字节Byte)
    • 值对Serial、ParNew收集器有效
  • -XX:MaxTenuringThreshold=15:默认值为15
    • 新生代每次MinorGC后,还存活的对象年龄+1,当对象的年龄大于设置的这个值时就进入老年代
  • -XX:+PrintTenuringDistribution:让JVM在每次MinorGC后打印出当前使用的Survivor中对象的年龄分布
  • -XX:TargetSurvivorRatio:表示MinorGC结束后Survivor区域中占用空间的期望比例
3.2.3 方法区

永久代

  • -XX:PermSize=256m:设置永久代初始值为256MB
  • -XX:MaxPermSize=256m:设置永久代最大值为256MB

元空间

  • -XX:MetaSpaceSize:初始空间大小
  • -XX:MaxMetaSpaceSize:最大空间,默认没有限制
  • -XX:+UseCompressedOops:压缩对象指针
  • -XX:+UseCompressedClassPointers:压缩类指针
  • -XX:CompressedClassSpaceSize:设置 Klass MetaSpace 的大小(默认1GB)
3.2.4 直接内存
  • -XX:MaxDirectMemorySize:指定DirectMemory容量,若未指定,则默认与Java堆最大值一样

3.3 OutOfMemory相关的选项

  • -XX:+HeapDumpOnOutOfMemory:表示在内存中出现OOM时,将Heap转存(Dump)到文件以便后续分析

  • -XX:+HeapDumpBeforeFullGC:表示在出现FullG前,生成Heap转储文件

  • -XX:HeapDumpPath=:指定Heap转储文件的存储路径

  • -XX:OnOutOfMemoryError:指定一个可行性程序或脚本的路径,当发生OOM时,去执行这个脚本

    • run.sh启动脚本中添加JVM参数:-XX:OutOfMemoryError=/opt/Server/restart.sh(以部署在Linux系统/opt/Server目录下的Server.jar为例)

    • restart.sh脚本(Linux环境)

      #!/bin/bash
      pid=$(ps -ef|grep Server.jar|awk '{if($8=="java") {print $2}}')
      kill -9 $pid
      cd /opt/Server/;sh run.sh
      
    • restart.bat脚本(Windows环境)

      echo off
      wmic process where Name='java.exe' delete
      cd D:/Server
      start run.bat
      

3.4 垃圾收集器相关选项

3.3.1 查看默认垃圾收集器

7款经典收集器与垃圾分代间的关系

【JVM · 调优】常用参数 & 垃圾回收_第7张图片

垃圾收集器组合关系

【JVM · 调优】常用参数 & 垃圾回收_第8张图片

查看默认垃圾收集器

  • -XX:+PrintCommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器)
  • 使用命令行指令:jinfo -flag <相关垃圾回收器参数> <进程ID>
3.3.2 Serial回收器

Serial收集器作为HotSpot中Client模式下的默认新生代垃圾收集器。Serial Old是运行在Client模式下默认的老年代垃圾回收器。

  • -XX:+UseSerialGC:指定年轻代和老年代都使用串行收集器。等价于 新生代用Serial GC,且老年代用 Serial Old GC。可以获得最高的单线程收集效率。
3.3.3 ParNew回收器
  • -XX:+UseParNewGC:手动指定使用ParNew收集器执行内存回收任务。它表示年轻代使用并行收集器,不影响老年代。
  • -XX:ParallelGCThreads=N:限制线程数量,默认开启和CPU数量相同的线程数。
3.3.4 Parallel回收器
  • -XX:+UseParallelGC:手动指定年轻代使用Parallel并行收集器执行内存回收任务。
  • -XX:+UseParallelOldGC:手动指定老年代都是使用并行收集器。
    • 分别适用于新生代和老年代,JDK8默认开启。
    • 上面两个参数,开启其中一个,另一个也会被开启(互相激活)
  • -XX:ParallelGCThreads:设置年轻代并行收集器的线程数。一般地,最好与CPU数量相等,以避免过多的线程数量影响垃圾收集新弄。
    • 在默认情况下,当CPU数量小于8个,ParallelGCThreads的值等于CPU数量。
    • 当CPU数量大于8个,ParallelGCThreads的值等于3 + ⌊[5 * CPU_COUNT] / 8⌋
  • -XX:MaxGCPauseMillis:设置垃圾收集器最大停顿时间 (即STW时间, 单位: 毫秒ms)。
    • 为了尽可能地把停顿时间控制在MaxGCPauseMills以内,收集器在工作时会调整Java堆大小或者其他一些参数。
    • 对于用户而言,停顿时间越短,体验越好。但是在服务器端,注重高并发、整体 吞吐量优先。所以服务器端适合Parallel,进行控制。
    • 该参数使用需谨慎。
  • -XX:GCTimeRatio:垃圾收集时间占总时间的比例1 / (N + 1)。用于衡量吞吐量的大小。
    • 区值范围(0,100),默认值99,即垃圾回收时间不超过1%。
    • 与前一个参数MaxGCPauseMillis有一定矛盾性。暂停时间越长,Ratio参数就越容易超过设定的比例。
  • -XX:+UseAdaptiveSizePolicy:设置Parallel Scavenge收集器具有 自适应调节策略
    • 在这种模式下,年轻代大小、Eden和Survivor的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量、停顿时间之间的平衡点。
    • 在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标吞吐量(GCTimeRatio)、停顿时间(MaxGCPauseMills),让虚拟机自己完成调优工作。
3.3.5 CMS回收器
  • -XX:+UseConcMarkSweepGC:手动指定使用CMS收集器执行内存回收任务。

    • 开启该参数会自动将-XX:+UseParNewGC打开。即:ParNew(Young区用)+CMS(Old区用)+Serial Old组合。
  • -XX:CMSInitiatingOccupanyFraction:设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收。

    • JDK5及以下版本的默认值为68,即当老年代的空间使用率达到68%时,会执行一次CMS回收。

      JDK6及以上版本默认值为92%。

    • 如果内存增长缓慢,则可以设置一个稍大的值,大的阈值可以有效降低CMS的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。因此 通过该选项便可以有效降低Full GC的执行次数。

  • -XX:+UseCMSCompactAtFullCollection:用于指定在执行完Full GC后堆内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变长了。

  • -XX:CMSFullGCsBeforeCompaction:设置在执行多次Full GC 后对内存空间进行压缩整理。

  • -XX:ParallelCMSThreads:设置CMS的线程数量。

    • CMS默认启动的线程数 (ParallelGCThreads + 3) / 4,ParallelGCThreads是年轻代并行收集器的线程数。当CPU资源比较紧张时,受到CMS收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕。

补充参数

另外,CMS收集器还有如下常用参数:

  • -XX:ConcGCThreads:设置并发垃圾收集的线程数,默认是基于ParallelGCThreads计算出来的。
  • -XX:+UseCMSInitiatingOccupancyOnly:是否动态可调,用这个参数可以使CMS一直按CMSInitiatingOccupancyFraction设定的值启动。
  • -XX:+CMSScavengeBeforeRemark:强制HotSpot虚拟机在CMS Remark阶段之前做一次MinorGC,用于提高Remark阶段的速度。
  • -XX:+CMSClassUnloadingEnable:如果有,则启用回收Perm区(JDK8之前)
  • -XX:+CMSParallelInitialEnabled:用于开启CMS Initial-mark阶段采用多线程 的方式进行标记,用于提高标记速度,在Java8开始已经默认开启。
  • -XX:+CMSParallelRemarkEnabled:用户开启CMS Remark阶段采用多线程的方式进行重新标记,默认开启。
  • -XX:+ExplicitGCInvokesConcurrent/-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses:这两个参数用户指定HotSpot虚拟机在执行System.gc()时使用CMS周期。
  • -XX:+CMSPrecleaningEnabled:指定CMS是否需要进行Pre cleaning这个阶段。

特别说明

  • JDK9新特性:CMS被标记为Deprecate(JEP291)
    • 如果对JDK 9及以上版本的HotSpot虚拟机使用参数-XX:+UseConcMarkSweepGC来开启CMS收集器时,用户会收到一个警告信息,提示CMS未来会被废弃。
  • JDK14新特性:删除CMS垃圾回收器(JEP363)
    • 移除CMS垃圾收集器,如果在JDK 14中使用-XX:+UseConcMarkSweepGC时,JVM不会报错,会给出一个Warning信息,不会exit。JVM自动回退以默认GC方式启动JVM。
3.3.6 G1回收器
  • -XX:+UseG1GC:手动指定使用G1收集器执行内存回收任务。
  • -XX:G1HeapRegionSize:设置每个Region大小。值是2的幂,范围是1MB~32MB,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。
  • -XX:MaxGCPauseMills:设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值为200ms。
  • -XX:ParallelGCThread:设置STW时GC线程数的值,最多设置为8。
  • -XX:ConcGCThreads:设置并发标记的线程数,将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右。
  • -XX:InitialtingHeapOccupancyPercent:设置触发并发GC周期的Java堆占用率阈值。超过此值,触发GC,默认值为45。
  • -XX:G1NewSizePercent/-XX:G1MaxNewSizePercent:新生代占用整个堆内存的最小百分比(默认5%)/最大百分比(默认60%)。
  • -XX:G1ReservePercent=10:保留内存区域,防止to space(Survivor中的to区)溢出。

Mixed GC 调优参数

注意,G1收集器主要涉及到Mixed GC,Mixed GC会回收Young区和部分Old区。

G1关于Mixed GC 调优常用参数:

  • -XX:InitiatingHeapOccupancyPercent:设置堆占用率的百分比(0~100)达到这个数值时会触发global concurrent marking(全局并发标记),默认为45%。值为0表示间断进行全局并发标记。
  • -XX:G1MixedGCLiveThresholdPercent:设置Old区的Region被回收时候的对象占比,默认占用率为85%。仅当Old区的Region中存活的对象占用达到了这个百分比,才会在Mixed GC中被回收。
  • -XX:G1HeapWastePercent:在global concurrent marking(全局并发标记)结束之后,可以知道所有的区有多少空间要被回收,在每次Young GC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC。
  • -XX:G1MixedGCCountTarget:一次global concurrent marking(全局并发标记)之后,最多执行Mixed GC的次数,默认是8。
  • -XX:G1OldCSetRegionThresholdPercent:设置Mixed GC收集周期中要收集的Old Region数的上限。默认值是Java堆的10%。
3.3.7 如何选择垃圾回收器
  • 优先调整堆大小让JVM自适应完成。
  • 如果内存小于100MB,使用串行收集器。
  • 如果是单核、单机程序,并且没有停顿时间的要求,使用串行收集器。
  • 如果是多CPU、需要高吞吐量、允许停顿时间超过1s,选择并行或JVM自行选择。
  • 如果是多CPU、追求低停顿,需快速响应(延迟不能超过1s,如互联网应用),使用并发收集器。官方推荐G1,性能高。

特别说明:

  1. 没有最好的收集器,更没有万能的收集器。
  2. 调优永远是针对特定场景、特定需求,不存在一劳永逸的收集器。

3.5 GC日志相关选项

常用参数

  • -verbose:gc:输出gc日志信息,默认输出到标准输出【可独立使用】
  • -XX:+PrintGC:等同于-verbose:gc,表示打开简化的GC日志【可独立使用】
  • -XX:+PrintGCDetails:在发生垃圾回收时打印内存回收详细的日志,并在进程退出时输出当前内存各区域分配情况【可独立使用】
  • -XX:+PrintGCTimeStamps:输出GC发生时的时间戳【需搭配PrintGCDetails使用】
  • -XX:+PrintGCDateStamps:输出GC发生时的时间戳(以日期的形式,如2013-05-04T21:53:59.234+0800)【需搭配PrintGCDetails使用】
  • -XX:+PrintHeapAtGC:每一次GC前后,都打印对堆信息【可独立/混合使用】
  • -Xloggc:把GC日志写入到一个文件中区,而非打印到标准输出中

其他参数

  • -XX:+TraceClassLoading:监控类的加载
  • -XX:+PrintGCApplicationStoppedTime:打印GC时线程的停顿时间
  • -XX:+PrintGCApplicationConcurrentTime:垃圾收集之前打印出未中断的执行时间
  • -XX:+PrintReferenceGC:记录回收了多少种不同引用类型的引用
  • -XX:+PrintTenuringDistribution:让JVM在每次MinorGC后打印出当前使用的Survivor中对象的年龄分布
  • -XX:+UseGCLogFileRotation:启用GC日志文件的自动转储
  • -XX:NumberOfGClogFiles=1:GC日志文件的循环数目
  • -XX:GCLogFileSize=1M:控制GC日志文件的大小

3.6 其他参数

  • -XX:+DisabledExplicitGC:禁止HotSpot执行System.gc(),默认禁用(允许执行System.gc())
  • -XX:ReservedCodeCacheSize=[g|m|k]/-XX:InitialCodeCacheSize=[g|m|k]:指定代码缓存大小
  • -XX:+UseCodeCacheFlushing:使用该参数让JVM放弃一些被编译的代码 (热点代码),避免代码缓存被占满时JVM切换到interpreted-only (仅解释模式) 的情况
  • -XX:+DoEscapeAnalysis:开启逃逸分析
  • -XX:+UseBiasedLocking:开启偏向锁
  • -XX:+UseLargePages:开启使用大页面
  • -XX:+UseTLAB:使用TLAB,默认打开
  • -XX:+PrintTLAB:打印TLAB使用情况
  • -XX:TLABSize:设置TLAB大小

4. 通过Java代码获取JVM参数

Java提供了java.lang.management包用于监视和管理Java虚拟机和Java运行时中的其他组件,它允许本地和远程监控和管理运行的Java虚拟机。其中ManagementFactory这个类较为常用。另外,还有Runtime类也可获取一些内存、CPU核数等相关数据。

通过这些API可以监控我们的应用服务器的堆内存使用情况,设置一些阈值进行报警等处理。

/**
 * 监控我们的应用服务器的堆内存使用情况,设置一些阈值进行预警处理。
 * -Xms100m -Xmx100m
 */
public class MemoryMonitor {
    public static void main(String[] args) {
        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
        MemoryUsage usage = memoryMXBean.getHeapMemoryUsage();
        System.out.println("INIT HEAP: " + usage.getInit() / 1024 / 1024 + "MB");
        System.out.println("MAX HEAP: " + usage.getMax() / 1024 / 1024 + "MB");
        System.out.println("USE HEAP: " + usage.getUsed() / 1024 / 1024 + "MB");
        System.out.println("\nFull Information: ");
        System.out.println("Heap Memory Usage: " + usage);
        System.out.println("Non-Heap Memory Usage" + memoryMXBean.getNonHeapMemoryUsage());

        System.out.println("==============通过Java来获取相关系统状态===============");
        System.out.println("当前堆内存大小 totalMemory " + (int)Runtime.getRuntime().totalMemory() / 1024 / 1024 + "MB");
        System.out.println("空闲堆内存大小 freeMemory " + (int)Runtime.getRuntime().freeMemory() / 1024 / 1024 + "MB");
        System.out.println("当前堆内存大小 maxMemory " + (int)Runtime.getRuntime().maxMemory() / 1024 / 1024 + "MB");
    }
}

【JVM · 调优】常用参数 & 垃圾回收_第9张图片

从Runtime中获取

public class HeapSpaceInitial {
    public static void main(String[] args) {
        // 返回Java虚拟机中的堆内存总量
        long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
        // 返回Java虚拟机视图使用的最大堆内存量
        long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

        System.out.println("-Xms : " + initialMemory + "MB");
        System.out.println("-Xmx : " + maxMemory + "MB");

        System.out.println("系统内存大小为:" + initialMemory * 4.0 / 1024 + "GB");
        System.out.println("系统内存大小为:" + maxMemory * 4.0 / 1024 + "GB");

    }
}

二. 分析GC日志

1. GC日志参数

  • -verbose:gc:输出GC日志,默认输出到白哦准输出
  • -XX:+PrintGC:输出GC日志,类比-verbose:gc
  • -XX:+PrintGCDetails:在发生垃圾回收时打印内存回收详细日志,并在进程退出时输出当前内存各区域分配情况
  • -XX:+PrintGCTimeStamps:输出GC发生时的时间戳
  • -XX:+PrintGCDateStamps:输出GC发生时的时间戳(以日期的形式,如2013-05-04T21:53:59.234+0800)
  • -XX:+PrintHeapAtGC:每一次GC前后,都打印堆信息
  • -Xloggc::表示把GC日志写入到一个文件中去,而不是打印到标准输出中

2. GC日志格式

2.1 复习:GC分类

针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大类型:一种是部分收集(Partial GC),一种是整堆收集(Full GC)

  • 部分收集:不是完整收集整个Java堆的垃圾收集,其中又分为:
    • 新生代收集(Minor GC / Young GC):只是新生代(Eden、S0 / S1)的垃圾收集。
    • 老年代收集(Major GC / Old GC):只是老年代的垃圾收集。
      • 目前,只有 CMS GC 会有单独收集老年代的行为。
      • 注意,很多时候 Major GC 会和 Full GC 混淆使用,需要具体分辨是老年代回收还是整堆回收。
    • 混合回收(Mixed GC):收集整个新生代以及部分老年代的垃圾收集。
      • 目前,只有 G1 GC 会有这种行为。
  • 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。
    • 哪些情况会触发Ful GC?
      • 老年代空间不足
      • 方法区空间不足
      • 显式调用System.gc()
      • Minor GC 进入老年代的数据的平均大小,大于老年代的可用内存
      • 大对象直接进入老年代,而老年代的可用空间不足

2.2 GC日志分类

Minor GC (Young GC / YGC) 日志

[GC (Allocation Failure) [PSYoungGen: 17156K->2544K(17920K)] 42032K->41804K(58880K), 0.0021751 secs] [Times: user=0.11 sys=0.05, real=0.00 secs] 

【JVM · 调优】常用参数 & 垃圾回收_第10张图片

Full GC 日志

Full GC (Ergonomics) [PSYoungGen: 2528K->1103K(17920K)] [ParOldGen: 39264K->40493K(40960K)] 41792K->41597K(58880K), [Metaspace: 3225K->3225K(1056768K)], 0.0113069 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

JDK 7:

【JVM · 调优】常用参数 & 垃圾回收_第11张图片

2.3 GC日志结构剖析

3.2.1 垃圾收集器
  • 使用Serial收集器在新生代的名字是Default New Generation,因此显示 “[DefNew”。
  • 使用ParNew收集器在新生代的名字会变成"[ParNew]",意思是"Parallel New Generation"。
  • 使用Parallel Scavenge收集器在新生代的名字是"[PSYoungGen",这里的JDK 1.7 使用的就是PSYoungGen。
  • 使用Parallel Old Generation收集器在老年代的名字是"[ParOldGen"
  • 使用G1收集器时,会显示"Garbage-First Heap"

Allocation Failure

表明本次因此GC的原因是因为在年轻代中没有足够的空间存储新的数据了

3.2.2 GC前后情况

通过图示,我们发现GC日志格式的规律一般都是:GC前内存占用->GC后内存占用 (该区域内存总大小)

[PSYoungGen: 5986k->696k(8704k)] 5986k->704k(9216k)

中括号内:GC回收前年轻代堆大小,回收后大小(年轻代堆总大小)

括号外:GC回收前年轻代和老年代大小,回收后大小(年轻代和老年代总大小)

3.2.3 GC时间

GC日志中有三个时间:user、sys、real

  • user:进程执行用户态代码(核心之外)所用的时间。这是执行此进程所使用的实际CPU时间,其他进程和此进程阻塞的时间并不包括在内。在垃圾收集的情况下,表示GC线程执行所使用的CPU总时间。
  • sys:进程在内核态消耗的CPU时间,即 在内核执行系统调用或等待系统时间所使用的CPU时间
  • real:程序从开始到结束所用的时钟时间。这个时间包括其他进程使用的时间片和进程阻塞的时间(如:等待I/O完成)。对于并行GC,这个数字应该接近 (用户时间+系统时间)/垃圾收集器使用线程数

由于多核的原因,一般的GC事件中,real time < sys + user time的,因为一般是多个线程并发GC,所以real time是要小于sys + user time的。若real > sys + user,则程序可能存在如下问题:IO负载重 / CPU不够用。

2.4 Minor GC 日志解析

2022-01-27T22:22:44.949+0800: 0.133: [GC (Allocation Failure) [PSYoungGen: 17156K->2544K(17920K)] 42032K->41804K(58880K), 0.0021751 secs] [Times: user=0.11 sys=0.05, real=0.00 secs] 
  • 2022-01-27T22:22:44.949+0800:日志打印时间 日期格式
  • 0.133:GC发生时,Java虚拟机启动以来经过的秒数
  • [GC (Allocation Failure):发生了一次垃圾回收,这是一次Minor GC。他不区分新生代GC还是老年代GC,括号里的内容是GC发生的原因,这里的Allocation Failure的原因是新生代中没有足够区域能够存放需要分配的数据而失败,
  • [PSYoungGen: 17156K->2544K(17920K)]
    • PSYoungGen:表示GC发生的区域,区域名称与使用的GC收集器是相关的
      • Serial收集器:Default New Generation,显示DefNew
      • ParNew收集器:ParNew
      • Parallel Scanvenge收集器:PSYoung
      • 老年代和新生代同理,与收集器名称相关
    • 17156K->2544K(17920K)GC前该内存区域已使用容量->GC后该区域容量(高区域总容量)
      • 如果是新生代,总容量则会显示整个新生代内存的9/10,即eden + from/to区
      • 如果是老年代,总容量则是全部内存大小,无变化
  • 42032K->41804K(58880K):在显示完区域容量GC的情况之后,会接着显示整个堆内存区域的GC情况:GC前堆内存已使用容量 >GC堆内存容量(堆内存总容量) (堆内存总容量 = 9/10新生代 + 老年代 < 初始化的内存大小)
  • , 0.0021751 secs]:整个GC所花费的时间(单位:秒)
  • [Times: user=0.11 sys=0.05, real=0.00 secs]
    • user:CPU工作在用户态所花费的时间
    • sys:CPU工作在内核态所花费的时间
    • real:在此次GC事件中所花费的总时间

2.5 Full GC 日志解析

2022-01-27T22:22:44.951+0800: 0.135: Full GC (Ergonomics) [PSYoungGen: 2528K->1103K(17920K)] [ParOldGen: 39264K->40493K(40960K)] 41792K->41597K(58880K), [Metaspace: 3225K->3225K(1056768K)], 0.0113069 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
  • 2022-01-27T22:22:44.951+0800:日志打印时间 日期格式

  • 0.135:GC发生时,Java虚拟机启动以来经过的秒数

  • Full GC (Ergonomics):发生了一次垃圾回收,这是一次Full GC,它不区分新生代GC还是老年代GC

    • 括号里的内容是GC发生的原因
      • Full GC (Ergonomics):JVM自适应调整导致的GC
      • Full GC (System):调用了System.gc()方法
      • Full GC (Metadata GC Threshold):MetaSapce区空间不足
  • [PSYoungGen: 2528K->1103K(17920K)]

    • PSYoungGen:表示GC发生的区域,区域名称与使用的GC收集器是相关的
      • Serial收集器:Default New Generation,显示DefNew
      • ParNew收集器:ParNew
      • Parallel Scanvenge收集器:PSYoung
      • 老年代和新生代同理,与收集器名称相关
    • 2528K->1103K(17920K)GC前该内存区域已使用容量->GC后该区域容量(高区域总容量)
      • 如果是新生代,总容量则会显示整个新生代内存的9/10,即eden + from/to区
      • 如果是老年代,总容量则是全部内存大小,无变化
  • [ParOldGen: 39264K->40493K(40960K)]

    • ParOldGen:表示GC发生的区域,区域名称与使用的GC收集器是相关的

    • 39264K->40493K(40960K)GC前该内存区域已使用容量->GC后该区域容量(高区域总容量)

  • 41792K->41597K(58880K):在显示完区域容量GC的情况之后,会接着显示整个堆内存区域的GC情况:GC前堆内存已使用容量 >GC堆内存容量(堆内存总容量) (堆内存总容量 = 9/10新生代 + 老年代 < 初始化的内存大小)

  • [Metaspace: 3225K->3225K(1056768K)]:MetaSpace回收情况

  • , 0.0113069 secs]:整个GC所花费的时间(单位:秒)

  • [Times: user=0.01 sys=0.00, real=0.01 secs]

    • user:CPU工作在用户态所花费的时间
    • sys:CPU工作在内核态所花费的时间
    • real:在此次GC事件中所花费的总时间

3. GC日志分析工具

通过日志可视化分析工具,我们可以很方便地看到JVM各个分代的内存使用情况、垃圾收集次数、垃圾回收原因、垃圾回收占用的时间、吞吐量等,这些指标在我们进行JVM调优时是很有用的。

  • -Xloggc:/path/to/gc.log:将GC日志存储到文件

3.1 GCEasy

基本概述

GCEasy——一款好用的在线分析GC日志网站

官网地址:点击前往

GCEasy是一款在线的GC日志分析器,可以通过GC日志分析进行内存泄漏检测、GC暂停原因分析、JVM配置建议优化等功能,而且免费(部分服务收费)

【JVM · 调优】常用参数 & 垃圾回收_第12张图片


/**
 * -Xms60m -Xmx60m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC
 */
public class GCLogTest {
    public static void main(String[] args) {
        ArrayList<byte[]> list = new ArrayList<>();

        for (int i = 0; i < 5000; i++) {
            byte[] arr = new byte[1024 * 50]; // 50KB
            list.add(arr);
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

部分分析图表展示:

【JVM · 调优】常用参数 & 垃圾回收_第13张图片

【JVM · 调优】常用参数 & 垃圾回收_第14张图片

【JVM · 调优】常用参数 & 垃圾回收_第15张图片

【JVM · 调优】常用参数 & 垃圾回收_第16张图片

3.2 GCViewer

基本概述

GCViewer是一个免费的、开源的、离线分析小工具,用于可视化查看由SUN/Oracle、IBM、HP、BEA Java虚拟机产生的垃圾收集器的日志。

GCViewer用于可视化Java VM选项-verbose:gc 和 .NET生成的数据-Xloggc:。它还计算与垃圾回收相关的性能指标(吞吐量、累积的暂停、最长的暂停等)。当通过更改时代大小或设置初始堆大小来调整特定应用程序的垃圾回收时,此功能非常有用。

安装

下载GCViewer工具

  • 源码下载:点此下载
  • 运行版本下载:点击下载

只需双击gcviewer-1.3x.ja或运行java -jar gcviewer-1.3x.jar(它需要运行java1.8 VM,即可启动 GCViewer (GUI))

【JVM · 调优】常用参数 & 垃圾回收_第17张图片

3.3 其他工具

3.3.1 GChisto

GChisto是一款专业分析GC日志的工具,可以通过GC日志来分析:Minor GC、Full GC的次数、频率、持续时间等,通过类列表、报表、图表等不同形式来反映GC的情况。

官网上没有下载网址,需要自行从SVN上拉下来编译。

这个工具已经不维护了,存在很多BUG。

3.3.2 HPjmeter

工具很强大,但只能打开有以下参数生成的GC log:verbose:gc/-Xloggc:gv.log,添加其他参数生成的gc.log无法打开。

HPjmeter集成了以前的HPjtune的功能,可以分析在HP机器上产生的垃圾回收日志。

你可能感兴趣的:(Java,#,JVM,java,jvm,性能优化)