JVM运行调优

笔记参考于尚硅谷宋红康:JVM全套教程:https://www.bilibili.com/video/BV1PJ411n7xZ

1.JVM运行时参数

1.JVM参数选项类型

类型一:标准参数选项

特点:

比较稳定,后续版本基本不会变化

以-开头

 

各种选项:

运行java或者java-help可以看到所有的标准选项

 

补充内容:

-server 与-client

Hotspot JVM有两种模式,分别是server和client,分别通过-server和-client模式设置:

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

关于server和client的官网介绍为:

Server-Class Machine Detection (oracle.com)

 

 

类型二:-X参数选项

特点:

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

 

各种选项:

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

 

JVM的JT编译模式相关的选项:

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

 

 

别地

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

 

 

类型三:-XX参数选项

特点:

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

作用:

用于开发和调试JVM

 

分类:

  • Boolean类型格式:

-XX:+<option> 表示启用 option 属性

-XX:-<option>表示禁用option属性

举例:

  • -XX:+UseParallelGC  不选择垃圾收集器为并行收集器
  • -XX:+UseG1GC 表示启用G1收集器
  • -XX:+UseAdaptiveSizePolicy 自动选择年轻代区大小和相应的Survivor区比例

 

说明:因为有的指令默认是开启的,所以可以使用-关闭

 

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

子类型1:数值型格式-XX:<option>=<number>:

number表示数值,number可以带上单位,比如:‘m’、‘M’表示兆,‘k’、‘K’ 表示Kb,’g’、‘G’表示 g(例如 32k跟32768是一样的效果)

例如:

表示设置新生代初始大小为1024兆:

-XX:NewSize=1024m

表示设置GC停顿时间: 500毫秒:

-XX:MaxGCPauseMillis=500

表示设置吞吐量:

-XX:GCTimeRatio=19

表示新生代与老年代的比例

-XX:NewRatio=2

 

子类型2:非数值型格式-XX:<name>=<string>

例如:

-XX:HeapDumpPath=/usr/local/heapdump.hprof

用来指定heap转存文件的存储路径。

 

特别地

输出所有参数的名称和默认值(:=表示修改过):

-XX:+PrintFlagsFinal

默认不包括Diagnostic和Experimental的参数

可以配合

-XX:+UnlockDiagnosticVMOptions

-XX:UnlockExperimentalVMOptions

使用

 

 

2.添加JVM参数选项

 

Eclipse:

 

 

IDEA:

 

运行jar包:

 

java -Xms50m -Xmx50m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -jar demo.jar

 

通过Tomcat运行war包:

Linux系统下可以在tomcat/bin/catalina.sh中添加类似如下配置:

JAVA_OPTS="-Xms512M -Xmx1024M"

Windows系统下在catalina.bat中添加类似如下配置:

set "JAVA_OPTS=-Xms512M -Xmx1024M"

 

程序运行过程中:

  • 使用jinfo -flag <name>=<value> <pid>设置非Boolean类型参数
  • 使用 jinfo -flag [+|-]<name> <pid>设置Boolean类型参数

 

3.常用的JVM参数选项

 

1.打印设置的XX项及值

可以让在程序运行前打印出用户手动设置或者JVM自动设置的XX选项:

-XX:+PrintCommandLineFlags

表示打印出所有XX选项的默认值:

-XX:+PrintFlagsInitial

表示打印出XX选项在运行程序时生效的值:

-XX:+PrintFlagsFinal

打印JVM的参数:

-XX:+PrintVMOptions

 

 

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

设置每个线程的栈大小为128k:(等价于-XX:ThreadStackSize=128k)

-Xss128k

 

  • 堆内存

等价于-XX:InitialHeapSize,设置JVM初始堆内存为3550M

-Xms3550m

 

等价于-XX:MaxHeapSize,设置JVM最大堆内存为3550M

-Xmx3550m

 

设置年轻代大小为2G(官方推荐配置为整个堆大小的3/8)。-XX:NewSize=1024m和-XX:MaxNewSize=1024m的整合

-Xmn2g

 

设置年轻代初始值为1024M:

-XX:NewSize=1024m

 

设置年轻代最大值为1024M

-XX:MaxNewSize=1024m

 

设置年轻代中Eden区与一个Survivor区的比值,默认为8:

-XX:SurvivorRatio=8

 

自动选择各区大小比例(默认开启),-XX:SurvivorRatio=8会失效,但开启,也不会生效。需要设置-XX:SurvivorRatio=8才会生效

-XX:+UseAdaptiveSizePolicy

 

设置老年代与年轻代(包括1个Eden和2个Survivor区)的比值

-XX:NewRatio=4

 

默认情况下,新生代占 1/3,老年代占2/3,其中,Eden默认占新生代的8/10,Survivor0,Survivor1各占新生代的1/10

 

设置让大于此阈值的对象直接分配在老年代,单位为字节,只对Serial、 ParNew收集器有效

-XX:PretenureSizeThredshold=1024

 

默认值为15,新生代每次MinorGC后,还存活的对象年龄+1,当对象的年龄大于设置的这个值时就进入老年代

-XX:MaxTenuringThreshold=15

 

让JVM在每次MinorGC后打印出当前使用的Survivor中对象的年龄分布

-XX:+PrintTenuringDistribution

 

表示MinorGC結束后Survivor区域中占用空间的期望比例

-XX:TargetSurvivorRatio

 

  • 方法区

永久代:

设置永久代初始值为256M

-XX:PermSize=256m

设置永久代最大值为256M

-XX:MaxPermSize=256m

 

 

元空间:

初始空间大小

-XX:MetaspaceSize

 

最大空间,默认没有限制

-XX:MaxMetaspaceSize

 

压缩对象指针

-XX:+UseCompressedOops

 

压缩类指针

-XX:+UseCompressedClassPointers

 

设置Klass Metaspace的大小,默认1G

-XX:CompressedClassSpaceSize

 

 

  • 直接内存

指定DirectMemory容量,若未指定,则默认与Java堆最大值一样:

-XX:MaxDirectMemorySize

 

 

3.OutofMemory相关的选项

表示在内存出现OOM的时候,把Heap转存(Dump)到文件以便后续分析:

-XX:+HeapDumpOnOutOfMemoryError

 

表示在每次出现FullGC之前,生成Heap转储文件:

-XX:+HeapDumpBeforeFullGC

 

指定heap转存文件的存储路径

-XX:HeapDumpPath=<path>

 

指定一个可行性程序或者脚本的路径,当发生OOM的时候,去执行这个脚本

-XX:OnOutOfMemoryError

 

对OnOutOfMemoryError的运维处理

以部署在linux系统/opt/Server目录下的Server.jar为例

1.在run.sh启动脚本中添加jvm参数:

 

-XX:OnOutOfMemoryError=/opt/Server/restart.sh

 

2.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
 

 

Windows环境:

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

 

 

4.垃圾收集器相关选项

 

  • 查看默认垃圾收集器

查看命令行相关参数(包含使用的垃圾收集器):

-XX:+PrintCommandLineFlags

 

使用命令行指令:

jinfo -flag 相关垃圾回收器参数 进程ID

 

  • Serial回收器

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

-XX:+UseSerialGC

指定年轻代和老年代都使用串行收集器。等价于新生代用Serial GC,且老年代用Serial old GC。可以获得最高的单线程收集效率。

 

  • ParNew回收器

手动指定使用ParNew收集器执行内存回收任务。它表示年轻代使用并行收集器,不影响老年代。

-XX:+UseParNewGC

 

制线程数量,默认开启和CPU数据相同的线程数。

-XX:ParallelGCThreads=N

 

  • 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的时间)。单位是毫秒。

为了尽可能地把停顿时间控制在MaxGCPauseMills以内,收集器在工作时会调整Java堆大小或者其他一些参数。

对于用户来讲,停顿时间越短体验越好。但是在服务器端,我们注重高并发,整体的吞吐量,所以服务器端适合Parallel,进行控制。

该参数使用需谨慎。

 

-XX:GCTimeRatio

垃圾收集时间占总时间的比例( =1 / (N + 1))。用于衡量吞吐量的大小。

取值范围(0,100)。默认值99,也就是垃圾回收时间不超过1%。

与前一个-XX:MaxGCPauseMillis参数有一定矛盾性。暂停时间越长,Radio参数就容易超过设定的比例。

 

-XX:+UseAdaptiveSizePolicy

设置Parallel Scavenge收集器具有自适应调节策略

在这种模式下,年轻代的大小、Eden和Survivor的比例、晋升老年代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点。

在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMills) ,让虚拟机自己完成调优工作

 

  • 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补充参数

设置并发垃圾收集的线程数,默认该值是基于ParallelGCThreads计算出来的;

-XX:ConcGCThreads

 

是否动态可调,用这个参数可以使CMS一直按CMSInitiatingOccupancyFraction设定的值启动

-XX:+UseCMSInitiatingOccupancyOnly

 

强制hotspot虚拟机在cms remark阶段之前做一次minor gc, 用于提高remark阶段的速度;

-XX:+CMSScavengeBeforeRemark

 

如果有的话,启用回收Perm 区 (JDK8之前)

-XX:+CMSClassUnloadingEnable

 

用于开启CMS initial-mark阶段采用多线程的方式进行标记,用于提高标记速度,在Java8开始已经默认开启;

-XX:+CMSParallelInitialEnabled

 

用户开启CMS remark阶段采用多线程的方式进行重新标记默认开启:

-XX:+CMSParallelRemarkEnabled

 

这两个参数用户指定hotspot虚拟机在执行System.gc()时使用CMS周期

-XX:+ExplicitGCInvokesConcurrent
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

 

指定CMS是否需要进行Pre cleaning这个阶段

-XX:+CMSPrecleaningEnabled

 

 

特别说明:

特别说明JDK9新特性: CMS被标记为Deprecate了(JEP291)

如果对JDK 9及以上版本的HotSpot虚拟机使用参数-XX:+UseConcMarkSweepGC来开启CMS收集器的话,用户会收到一个警告信息,提示CMS未来将会被废弃。

JDK14新特性:删除CMS垃圾回收器(JEP363)

移除了CMS垃圾收集器,如果在JDK14中使用-XX:+UseConcMarkSweepGC的话JVM不会报错,只是给出一个warning信息,但是不会exit。JVM会自动回退以默认GC方式启动JVM

 

  • G1回收器

手动指定使用G1收集器执行内存回收任务。

-XX: +UseG1GC

 

设置每个Region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。

-XX:G1HeapRegionSize

 

设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms

-XX:MaxGCPauseMillis

 

设置STW时GC线程数的值。最多设置为8

-XX:ParallelGCThread

 

设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右。

-XX:ConcGCThreads

 

设置触发并发GC周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45。

-XX:InitiatingHeapOccupancyPercent

 

新生代占用整个堆内存的最小百分比(默认5%)、最大百分比(默认60%)

-XX:G1NewSizePercent
-XX:G1MaxNewSizePercent

 

保留内存区域,防止to space (Survivor中的to区)溢出

-XX:G1ReservePercent=10

 

  • Mixed GC调优参数

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

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

设置堆占用率的百分比(0到100)达到这个数值的时候触发global concurrent marking (全局并发标记) ,默认为45%。值为0表示间断进行全局并发标记。

-XX:InitiatingHeapOccupancyPercent:

 

设置Old区的region被回收时候的对象占比,默认占用率为85%,只有0ld区的region中存活的对象占用达到了这个百分比,才会在Mixed GC中被回收。

-XX:G1MixedGCLiveThresholdPercent

 

在global concurrent marking (全局并发标记)结束之后,可以知道所有的区有多少空间要被回收,在每次young GC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC

-XX:G1HeapWastePercent

 

一次global concurrent marking (全局并发标记)之后,最多执行Mixed GC的次数,默认是8。

-XX:G1MixedGCCountTarget

 

设置Mixed GC收集周期中要收集的Old region数的上限。默认值是Java堆的10%

-XX:G1O1dCSetRegionThresholdPercent

 

 

  • 怎么选择垃圾回收器

优先调整堆的大小让JVM自适应完成。

如果内存小于100M,使用串行收集器

如果是单核、单机程序,并且没有停顿时间的要求,串行收集器

如果是多CPU、需要高吞吐量、允许停顿时间超过1秒,选择并行或者JVM自己选择

如果是多CPU、追求低停顿时间,需快速响应(比如延迟不能超过1秒,如互联网应用),使用并发收集器。官方推荐G1,性能高。现在互联网的项目,基本都是使用G1。

 

特别说明:

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

 

5.GC日志相关选项

  • 常用参数:

输出gc日志信息,默认输出到标准输出

-verbose:gc

 

等同于-verbose:gc,表示打开简化的GC日志

-XX:+PrintGC

 

在发生垃圾回收时打印内存回收详细的日志,并在进程退出时输出当前内存各区域分配情况

-XX:+PrintGCDetails

 

 

输出GC发生时的时间戳(需要与-XX:+PrintGCDetails共同使用)

-XX:+PrintGCTimeStamps

 

输出GC发生时的时间戳(以日期的形式,如2013-05-04T21:53:59.234+0800)(需要与-XX:+PrintGCDetails共同使用)

-XX:+PrintGCDateStamps

 

每一次GC前和GC后,都打印堆信息

-XX:+PrintHeapAtGC

 

把GC日志写入到一个文件中去,而不是打印到标准输出中

-Xloggc:<file>

如:

-Xloggc:d:/heaplog.log

 

  • 其他参数:

监控类的加载

-XX:+TraceClassLoading

 

打印GC时线程的停顿时间

-XX:+PrintGCApplicationStoppedTime

 

垃圾收集之前打印出应用未中断的执行时间

-XX:+PrintGCApplicationConcurrentTime

 

记录回收了多少种不同引用类型的引用

-XX:+PrintReferenceGC

 

让JVM在每次MinorGC后打印出当前使用的Survivor中对象的年龄分布

-XX:+PrintTenuringDistribution

 

启用GC日志文件的自动转储

-XX:+UseGCLogFileRotation

 

GC日志文件的循环数目

-XX:NumberOfGClogFiles=1

 

控制GC日志文件的大小

-XX:GCLogFileSize=1M

 

 

6.其他参数

禁止hotspot执行System.gc(),默认禁用

-XX:+DisableExplicitGC

 

指定代码缓存的大小

-XX:ReservedCodeCacheSize= <n>[g|m|k]
-XX:InitialCodeCacheSize= <n>[g|m|k]

 

使用该参数让jvm放弃一些被编译的代码,避免代码缓存被占满时JVM切换到interpreted-only的情况

-XX:+UseCodeCacheFlushing

 

开启逃逸分析

-XX:+DoEscapeAnalysis

 

开启偏向锁

-XX:+UseBiasedLocking

 

开启使用大页面

-XX:+UseLargePages

 

使用TLAB,默认打开

-XX:+UseTLAB

 

打印TLAB的使用情况

-XX:+PrintTLAB

 

设置TLAB大小

-XX:TLABSize

 

 

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

Java提供了java.lang.management包用于监视和管理Java虚拟机和Java运行时中的其他组件,它允许本地和远程监控和管理运行的Java虚拟机。其中ManagementFactory这个类还是挺常用的。另外还有Runtime类也可以获取一些内存、CPU核数等相关的数据。通过这些api可以监控我们的应用服务器的堆内存使用情况,设置一些阈值进行报警等处理。

 

package com.yutian.java;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;

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

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

    }
}

 

 

2.分析GC日志

1.GC日志参数

输出gc日志信息,默认输出到标准输出

-verbose:gc

 

等同于-verbose:gc,表示打开简化的GC日志

-XX:+PrintGC

 

在发生垃圾回收时打印内存回收详细的日志,并在进程退出时输出当前内存各区域分配情况

-XX:+PrintGCDetails

 

 

输出GC发生时的时间戳(需要与-XX:+PrintGCDetails共同使用)

-XX:+PrintGCTimeStamps

 

输出GC发生时的时间戳(以日期的形式,如2013-05-04T21:53:59.234+0800)(需要与-XX:+PrintGCDetails共同使用)

-XX:+PrintGCDateStamps

 

每一次GC前和GC后,都打印堆信息

-XX:+PrintHeapAtGC

 

 

把GC日志写入到一个文件中去,而不是打印到标准输出中

-Xloggc:<file>

如:

-Xloggc:d:/heaplog.log

 

 

 

2.GC日志格式

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堆和方法区的垃圾收集。

 

哪些情况会触发Full GC?

  • 老年代空间不足
  • 方法区空间不足
  • 显式调用System.gc()
  • Minor GC进入老年代的数据的平均大小 大于 老年代的可用内存
  • 大对象直接进入老年代,而老年代的可用空间不足

 

2.GC日志分类

1.MinorGC

 

 

2.FullGC

 

 

 

 

3.GC日志结构剖析

  • 使用Serial收集器在新生代的名字是Default New Generation,因此显示的是”[DefNew”
  • 使用ParNew收集器在新生代的名字会变成”[ParNew”,意思是”Parallel New Generation”
  • 使用Parallel Scavenge收集器在新生代的名字是”[PSYoungGen”,这里的JDK1.7使用的就是PSYoungGen
  • 使用Parallel Old Generation收集器在老年代的名字是”[ParOldGen”
  • 使用G1收集器的话,会显示为”garbage-first heap”

 

Allocation Failure表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了。

 

 

GC前后情况:

通过图示,我们可以发现GC日志格式的规律一般都是:

GC前内存占用一>GC后内存占用(该区域内存总大小)

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

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

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

 

GC时间

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

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

由于多核的原因,一般的GC事件中, real time是小于sys + user time的,因为一般是多个线程并发的去做GC,所以real time是要小于sys+user time的。如果real>sys+user的话,则你的应用可能存在下列问题:IO负载非常重或者是CPU不够用。

 

4.Minor GC 日志解析

2023-08-10T22:48:26.909+0800: 4.176: [GC (Allocation Failure) [PSYoungGen: 16331K->2016K(18432K)] 16331K->13966K(59392K), 0.0054547 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
2023-08-10T22:48:32.014+0800: 9.282: [GC (Allocation Failure) [PSYoungGen: 18400K->2040K(18432K)] 30350K->30392K(59392K), 0.0055397 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
2023-08-10T22:48:32.019+0800: 9.288: [Full GC (Ergonomics) [PSYoungGen: 2040K->0K(18432K)] [ParOldGen: 28352K->30080K(40960K)] 30392K->30080K(59392K), [Metaspace: 3780K->3780K(1056768K)], 0.0086028 secs] [Times: user=0.20 sys=0.00, real=0.01 secs]

 

  • 2023-08-10T22:48:26.909+0800:日志打印时间日期格式 如2013-05-04T21:53:59.234+0800
  • 0.822:gc发生时,Java虚拟机启动以来经过的秒数
  • [GC (Allocation Failure):发生了一次垃圾回收,这是一次Minor GC.它不区分新生代GC还是老年代GC,括号里的内容是gc发生的原因,这里的Allocation Failure的原因是新生代中没有足够区域能够存放需要分配的数据而失败.
  • [PSYoungGen: 16331K->2016K(18432K)]:PSYoungGen:表示GC发生的区域,区域名称与使用的GC收集器是密切相关的(Serial收集器: Default New Generation 显示DefNew。ParNew收集器: ParNew。Parallel Scanvenge收集器: PSYoung。老年代和新生代同理,也是和收集器名称相关)16331K->2016K(18432K): GC前该内存区域已使用容量->GC后该区域容量(该区域总容量)【如果是新生代,总容量则会显示整个新生代内存的9/10,即eden+from/to 区,如果是老年代,总容量则是全部内存大小,无变化】
  • 16331K->13966K(59392K):在显示完区域容量GC的情况之后,会接着显示整个堆内存区域的GC情况:GC前堆内存已使用容量->GC堆内存容量(堆内存总容量)【堆内存总容量=9/10新生代+老年代<初始化的内存大小】
  • , 0.0054547 secs]:整个GC所花费的时间,单位是秒
  • [Times: user=0.00 sys=0.00, real=0.01 secs]:user:指的是CPU工作在用户态所花费的时间,sys:指的是CPU工作在内核态所花费的时间,real:指的是在此次GC事件中所花费的总时间

 

 

5.Full GC 日志解析

2023-08-10T22:48:32.019+0800: 9.288: [Full GC (Ergonomics) [PSYoungGen: 2040K->0K(18432K)] [ParOldGen: 28352K->30080K(40960K)] 30392K->30080K(59392K), [Metaspace: 3780K->3780K(1056768K)], 0.0086028 secs] [Times: user=0.20 sys=0.00, real=0.01 secs] 
2023-08-10T22:48:37.166+0800: 14.434: [Full GC (Ergonomics) [PSYoungGen: 16315K->5500K(18432K)] [ParOldGen: 30080K->40877K(40960K)] 46395K->46378K(59392K), [Metaspace: 3781K->3781K(1056768K)], 0.0166588 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]

 

  • 2023-08-10T22:48:32.019+0800:日志打印时间日期格式 如2013-05-04T21:53:59.234+0800
  • 9.288:gc发生时,Java虚拟机启动以来经过的秒数
  • [Full GC (Ergonomics):发生了一次垃圾回收,这是一次FULL GC.它不区分新生代GC还是老年代GC括号里的内容是gc发生的原因【Metadata GC Threshold的原因是Metaspace区不够用了。Full GC (Ergonomics) : JVM自适应调整导致的GC。Full GC (System) : 调用了System.gc()方法】
  • [PSYoungGen: 2040K->0K(18432K)]:PSYoungGen:表示GC发生的区域,区域名称与使用的GC收集器是密切相关的(Serial收集器: Default New Generation 显示DefNew。ParNew收集器: ParNew。Parallel Scanvenge收集器: PSYoung。老年代和新生代同理,也是和收集器名称相关)2040K->0K(18432K): GC前该内存区域已使用容量->GC后该区域容量(该区域总容量)【如果是新生代,总容量则会显示整个新生代内存的9/10,即eden+from/to 区,如果是老年代,总容量则是全部内存大小,无变化】
  • [ParOldGen: 28352K->30080K(40960K)]:老年代区域没有发生GC,因为本次GC是Ergonomics引起的
  • 30392K->30080K(59392K),在显示完区域容量GC的情况之后,会接着显示整个堆内存区域的GC情况:GC 前堆内存已使用容量->GC 堆内存容量(堆内存总容量)【堆内存总容量=9/10新生代+老年代<初始化的内存大小】
  • [Metaspace: 3780K->3780K(1056768K)],:metaspace GC 回收0K空间
  • 0.0086028 secs]:整个GC所花费的时间,单位是秒
  • [Times: user=0.20 sys=0.00, real=0.01 secs]:user:指的是CPU工作在用户态所花费的时间,sys:指的是CPU工作在内核态所花费的时间,real:指的是在此次GC事件中所花费的总时间

 

 

 

3.GC日志分析工具

 

代码一:

package com.yutian.java;

import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
* java.lang.OutOfMemoryError: Metaspace异常演示:
*
* -Xms60m -Xmx60m -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC
*
*/
public class MetaspaceOOM extends ClassLoader {
    public static void main(String[] args) {
        int j = 0;
        try {
            MetaspaceOOM test = new MetaspaceOOM();
            for (int i = 0; i < 10000; i++) {
                //创建ClassWriter对象,用于生成类的二进制字节码
                ClassWriter classWriter = new ClassWriter(0);
                //指明版本号,修饰符,类名,包名,父类,接口
                classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                //返回byte[]
                byte[] code = classWriter.toByteArray();
                //类的加载
                test.defineClass("Class" + i, code, 0, code.length);//Class对象
                j++;
            }
        } finally {
            System.out.println(j);
        }
    }
}

 

-Xms60m -Xmx60m -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:d:/MAT_log/heaplog.log

 

代码二:

package com.yutian.java;

import java.util.ArrayList;

/**
* 测试生成详细的日志文件
*
* -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();
            }
        }

    }
}

 

-Xms60m -Xmx60m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:d:/MAT_log/GClog.log

 

 

GCeasy:

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

官网地址: https://gceasy.io/

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

 

 

 

 

基本概述上面介绍了一款在线的GC日志分析器,下面介绍一个离线版的GCViewer。

https://github.com/chewiebug/GCViewer.git

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

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

 

 

参考资料:

尚硅谷宋红康:JVM全套教程:https://www.bilibili.com/video/BV1PJ411n7xZ

周志明:深入理解java虚拟机

Java虚拟机规范:Chapter 4. The class File Format (oracle.com)

[性能监控与分析] Java的浅堆和深堆_shangshanzixu的博客-CSDN博客

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇