JVM好好学四(虚拟机工具和性能调优)

JVM好好学四(虚拟机工具和性能调优)

大纲

  • 1、JVM参数和性能调优

  • 2、JVM调优工具 (jvisualvm、jstat、jstack、jmap、)

1、JVM参数和性能调优

jvm 参数可分为三类:

(1)标准参数: 以 “-“ 开头的参数

(2)非标准参数: 以 “-X“ 开头的参数

(3)不稳定参数 : 以”-XX“ 开头的参数

1.1 标准参数

// 打开一个命令终端,执行 java -help,就可以展示所有的JVM标准参数。
java -version
java -showversion
java -help

1.2 非标准参数

        非标准参数表示不保证所有JVM实现都支持这些参数,在将来的JVM版本中可能会发生改变。非标准参数统一以 -X 开头,如 -Xmx20M 设置最大java堆大小,示例:

java -classpath E:/code -Dfile.encoding=UTF-8 -Dprofile=dev -Xmx20M HelloWorld tom jack
  • 查看所有非标准参数

打开一个命令终端,执行 java -X,就可以展示所有的JVM非标准参数。

1.3 不稳定参数

        不稳定参数这是我们日常开发中接触到最多的参数类型,也是非标准化参数,相对来说不稳定,随着JVM版本的变化可能会发生变化,主要用于JVM调优和debug。

        不稳定参数统一以 “-XX“ 开头,书写格式分为两种

  • bool 类型:
    • -XX:+<option>:代表启用 true
    • -XX:-<option>:代表禁用 false
  • 数值或字符串类型:
    • -XX:<option>=<number>:数字如果有单位一般是 兆字节的“ m”或“ M”,千字节的“ k”或“ K”以及千兆字节的“ g”或“ G”(例如32k与32768相同)
    • -XX:<option>=<string>:字符串通常用于指定文件,路径或命令列表

        如打印GC日志 -XX:+PrintGCDetails、设置对象最大晋升老年代的年龄 -XX:MaxTenuringThreshold=15,示例:

java -classpath E:/code -Dfile.encoding=UTF-8 -Dprofile=dev -Xmx20M -XX:+PrintGCDetails -XX:MaxTenuringThreshold=15  HelloWorld tom jack

1.4 常见的jvm参数配置说明

  • 堆大小典型配置参数
// (1) -Xmx 设置最大堆的内存大小
示例:-Xmx4096m , 设置jvm最大可用内存4096m


// (2) -Xms 设置堆初始内存大小
示例: -Xms4096m,设置jvm初始内存4096m

// (3) -Xmn 设置堆年轻代大小 
示例: -Xmn2g 设置年轻代大小为2 GB。整个JVM内存大小=年轻代大小+老年代大小+持久代大小
持久代一般固定大小为64 MB,所以增大年轻代后,将会减小年老代大小。
此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8 

// (4) -Xss: 设置线程的栈大小 
示例:-Xss128k 设置每个线程的栈大小为128 KB    
说明:JDK 5.0版本以后每个线程栈大小为1 MB,JDK 5.0以前版本每个线程栈大小为256 KB。
请依据应用的线程所需内存大小进行调整。在相同物理内存下,减小该值可以生成更多的线程。
但是操作系统对一个进程内的线程个数有一定的限制,无法无限生成,一般在3000个~5000个。 

// (5) -XX:NewRatio=n:设置年轻代和年老代的比值。
示例: -XX:NewRatio=4,设置年轻代(包括Eden和两个Survivor区)与年老代的比值
(除去持久代)。如果设置为4,那么年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5。 

// (6) -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。
示例:-XX:SurvivorRatio=4,设置年轻代中Eden区与Survivor区的大小比值。
如果设置为4,那么两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代
的1/6. 

// (7) -XX:MaxPermSize=n:设置持久代大小。
示例: -XX:MaxPermSize=16m,设置持久代大小为16 MB  

// (8) -XX:MaxTenuringThreshold=n:设置垃圾最大年龄。
 -XX:MaxTenuringThreshold=0,设置垃圾最大年龄。如果设置为0,那么年轻代对象不经过
Survivor区,直接进入年老代。对于年老代比较多的应用,提高了效率。如果将此值设置为较
大值,那么年轻代对象会在Survivor区进行多次复制,增加了对象在年轻代的存活时间,
增加在年轻代即被回收的概率。 
  • 调优回收器GC
// (1) -XX:+UseParallelGC:选择垃圾收集器为并行收集器。
示例:-Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20,-XX:+UseParallelGC
此配置仅对年轻代有效,即在示例配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。 

// (2) -XX:ParallelGCThreads:配置并行收集器的线程数,即同时多少个线程一起进行垃圾回收。此值建议配置与处理器数目相等。
示例:-Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20,-XX:ParallelGCThreads=20
表示配置并行收集器的线程数为20个。 

// (3) -XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集 
示例:-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC,-XX:+UseParallelOldGC
表示对年老代进行并行收集。说明:JDK 6.0支持对年老代并行收集。 

// (4) -XX:MaxGCPauseMillis:设置每次年轻代垃圾回收的最长时间,
如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
 示例:-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100,-XX:MaxGCPauseMillis=100
设置每次年轻代垃圾回收的最长时间为100 ms 

// (5) -XX:+UseAdaptiveSizePolicy:
设置此选项后,并行收集器自动选择年轻代区大小和相应的Survivor区比例,
以达到目标系统规定的最低响应时该间或者收集频率,该值建议使用并行收集器时,并且一直打开。
示例:-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy 
  • 响应时间优先的GC典型配置参数
// (1) -XX:+UseConcMarkSweepGC 设置年老代为并发收集
示例:-Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 
// (2) -XX:+UseParNewGC 设置年轻代为并行收集。可与CMS收集同时使用 
示例: -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
JDK 5.0以上版本,JVM根据系统配置自行设置,无需再设置此值 
// (3) -XX:CMSFullGCsBeforeCompaction 由于并发收集器不对内存空间进行压缩、整理,
所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对
内存空间进行压缩、整理 
示例: -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection`,`-XX:CMSFullGCsBeforeCompaction=5 
说明: 表示运行GC5次后对内存空间进行压缩、整理 

// (4) -XX:+UseCMSCompactAtFullCollection  打开对年老代的压缩。该值可能会影响性能,但是可以消除碎片 
示例: -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection 
  • 用于辅助的GC典型配置参数

  • -XX:+PrintGC:用于输出GC日志。

  • -XX:+PrintGCDetails:用于输出GC日志。

  • -XX:+PrintGCTimeStamps:用于输出GC时间戳(JVM启动到当前日期的总时长的时间戳形式)。示例如下:0.855: [GC (Allocation Failure) [PSYoungGen: 33280K->5118K(38400K)] 33280K->5663K(125952K), 0.0067629 secs] [Times: user=0.01 sys=0.01, real=0.00 secs]

  • -XX:+PrintGCDateStamps:用于输出GC时间戳(日期形式)。示例如下:2022-01-27T16:22:20.885+0800: 0.299: [GC pause (G1 Evacuation Pause) (young), 0.0036685 secs]

  • -XX:+PrintHeapAtGC:在进行GC前后打印出堆的信息。

  • -Xloggc:../logs/gc.log:日志文件的输出路径。

1.5 JVM内存配置最佳实战

        当设置的JVM堆空间过小时,程序会出现系统内存不足OOM(Out of Memory)的问题。特别是在容器环境下,不合理的JVM堆参数设置会导致各种异常现象产生,例如应用堆大小还未到达设置阈值或规格限制,就因为OOM导致重启等。

  • 通过MaxRAMPercentage限制堆大小(推荐)

        在容器环境下,Java只能获取服务器的配置,无法感知容器内存限制。可以通过设置-Xmx来限制JVM堆大小,但该方式存在以下问题:

  • 当规格大小调整后,需要重新设置堆大小参数。
  • 当参数设置不合理时,会出现应用堆大小未达到阈值但容器OOM被强制关闭的情况。

应用程序出现OOM问题时,会触发Linux内核的OOM Killer机制。该机制能够监控占用过大内存,尤其是瞬间消耗大量内存的进程,然后它会强制关闭某项进程以腾出内存留给系统,避免系统立刻崩溃。

推荐的JVM参数设置:

-XX:+UseContainerSupport -XX:MaxRAMPercentage=70.0 -XX:+PrintGCDetails 
-XX:+PrintGCDateStamps -Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').log 
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof

参数说明如下:

参数说明
-XX:+UseContainerSupport使用容器内存。允许JVM从主机读取cgroup限制,例如可用的CPU和RAM,并进行相应的配置。当容器超过内存限制时,会抛出OOM异常,而不是强制关闭容器。
-XX:MaxRAMPercentage设置JVM使用容器内存百分比。由于存在系统组件开销,建议最大不超过75.0,推荐设置为70.0。
-XX:+PrintGCDetails输出GC详细信息。
-XX:+PrintGCDateStamps输出GC时间戳。日期形式,例如2019-12-24T21:53:59.234+0800。
-Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').logGC日志文件路径。需保证Log文件所在容器路径已存在,建议您将该容器路径挂载到NAS目录,以便自动创建目录以及实现日志的持久化存储。
-XX:+HeapDumpOnOutOfMemoryErrorJVM发生OOM时,自动生成DUMP文件。
-XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprofDUMP文件路径。需保证DUMP文件所在容器路径已存在,建议您将该容器路径挂载到NAS目录,以便自动创建目录以及实现日志的持久化存储。
  • 该特性支持JDK 8u191+、JDK 10及以上版本。

  • 如果您没有将文件挂载到NAS目录,必须保证/home/admin/nas路径存在,否则不会产生日志。

  • 通过Xms Xmx限制堆大小

可以通过设置-Xms-Xmx来限制堆大小,推荐的JVM参数设置:

-Xms2048m -Xmx2048m -XX:+PrintGCDetails -XX:+PrintGCDateStamps 
-Xloggc:/home/admin/nas/gc-${POD_IP}-$(date '+%s').log 
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof

推荐的堆大小设置:

内存规格大小JVM堆大小
1 GB600 MB
2 GB1434 MB
4 GB2867 MB
8 GB5734 MB

2、JVM调优工具

2.1 jvisualvm

        平时经常用jvisualvm来分析dum.hprof文件,也可以用来分析GC的一些情况。通常对gc日志的分析也可以通过这个网站来分析,GC日志分析网站,只需要上传gc文件即可。

  • spaces:代表虚拟机内存分布情况,虚拟机被分为Metaspace(元空间)、 Old、Eden、S0、S1
  • graphs:内存使用详细介绍
Compile Time(编译时间):
11350 compiles 表示编译总数,37.873s表示编译累计 时间。一个脉冲表示一次JIT编译,
窄脉冲表示持续时间短,宽脉冲表示持续时间长。 

Class Loader Time(类加载时间):9577 loaded表示加载类数量, 391 unloaded 表示卸载的类数量,
3.566s表示类加载花费的时间

GC Time(GC Time):238 collections表示垃圾收集的总次数,1.378s表示垃圾 
收集花费的时间,last cause表示最近垃圾收集的原因

Eden Space(Eden 区):(3.924g表示最大容量,115.00M表示当前容量),
13.000M表示当前使用情况,238 collections表示垃圾收集次数,
1.378s表示垃 圾收集花费时间

Survivor 0/Survivor 1(S0和S1区):(8M表示最大容量,8M表示当前容量),
之后的值是0M是当前使用情况

Old Gen(老年代):(3.924G表示最大容量,184M表示当前容量), 
152.219M表示当前使用情况,0 collections表示垃圾收集次数 ,0s表示垃圾收集花费时间

Metaspace(元空间):(1.051G表示最大容量,61.777M表示当前容量),
60.616M表示当前使用情
  • histogramspaces区域:survivor区域参数跟年龄柱状图

2.2 Jstat命令

        jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

命令参数如下:

  • -class参数,统计类加载信息
root@fam30-10-20-146-65 ~$ jstat -class 95865
Loaded  Bytes  Unloaded  Bytes     Time   
 19492 37777.6        0     0.0      29.46

参数说明:
Loaded  已加载的class数量 
Bytes   已加载的class占用空间大小 
Unloaded  未加载的class数量 
Bytes    未加载的class占用空间大小 
Time     类加载耗时 

间隔1000ms 打印5次
root@fam30-10-20-146-65 ~$ jstat -class 95865 1000 5
  • -compiler (查看JIT编译统计)
root@fam30-10-20-146-65 ~$ jstat -compiler 95865
Compiled Failed Invalid   Time   FailedType FailedMethod
23590     14       0   215.80          1 com/alibaba/druid/pool/DruidDataSource shrink 

参数说明:
Compiled  JIT编译的class数量
Failed  JIT编译失败的数量
Invalid    不可用数量
Time   时间
FailedType   失败类型
FailedMethod 失败方法
  • -gc (统计垃圾回收)
root@fam30-10-20-146-65 ~$ jstat -gc  95865
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
28160.0 26624.0 608.0   0.0   644096.0 141070.0 1398272.0   135142.9  127616.0 121632.7 14464.0 13379.6     50    2.511   4      1.386    3.897

-- 百分比显示
root@fam30-10-20-146-65 ~$ jstat -gcutil  95865
-- 间隔1s打印10次
root@fam30-10-20-146-65 ~$ jstat -gc -t 95865 1000 10

参数说明:
S0C 第一个Survivor区的大小(KB)
S1C 第二个Survivor区的大小(KB)
S0U 第一个Survivor区的使用大小(KB)
S1U 第二个Survivor区的使用大小(KB)
EC Eden区的大小(KB) 
EU Eden区的使用大小(KB)
OC Old区的大小(KB)
OU Old区的使用大小(KB) 
MC 方法区的大小(KB) 
MU 方法区的使用大小(KB) 
CCSC 压缩类的空间大小(KB) 
CCSU 压缩类的空间使用大小(KB) 
YGC 年轻代垃圾回收次数 
YGCT 年轻代垃圾回收消耗时间 
FGC  老年代垃圾回收次数 
FGCT 老年代垃圾回收消耗时间 
GCT 总的垃圾回收消耗时间
  • 如何判断可能存在的内存泄露?

(1)第1步在长时间运行的Java程序中,我们可以运行jstat命令连续获取多行性能数据,并取这几行数据中OU列(即已占用的老年代内存)的最小值。

(2)第2步:然后,我们每隔一段较长的时间重复一次上述操作,来获得多组OU最小值。如果这些值呈上涨趋势,则说明该Java程序的老年代内存已使用率在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄露。

2.3 Jstack命令(Java堆栈跟踪)

        jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。

        生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用 jstack 显示各个线程调用的堆栈情况。

        在 thread dump 中,要留意下面几种状态:

死锁,Deadlock(重点关注)
等待资源,Waiting on condition(重点关注)
等待获取监视器,Waiting on monitor entry(重点关注)
阻塞,Blocked(重点关注)
执行中,Runnable
暂停,Suspended
对象等待中,Object.wait() 或 TIMED_WAITING
停止,Parked

option参数:

-F
当正常输出的请求不被响应时,强制输出线程堆栈 
-l
除堆栈外,显示关于锁的附加信息 
-m 
如果调用本地方法的话,可以显示 C/C++的堆栈 

// 打印线程的堆栈信息
jstack -l pid >> jstack.info

2.3.1 线程的状态

Java线程的六种状态:

(1)New:创建后尚未启动的线程处于这种状态,不会出现在Dump中。
(2)RUNNABLE:包括Running和Ready。线程开启start()方法,会进入该状态,在虚拟机内执行的。
(3)Waiting:无限的等待另一个线程的特定操作。
(4)Timed Waiting:有时限的等待另一个线程的特定操作。
(5)阻塞(Blocked):在程序等待进入同步区域的时候,线程将进入这种状态,在等待监视器锁。
(6)结束(Terminated):已终止线程的线程状态,线程已经结束执行。

2.3.2 Monitor 监视锁

        在多线程的 JAVA程序中,实现线程之间的同步,就要说说 Monitor。 Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。下 面这个图,描述了线程和 Monitor之间关系,以 及线程的状态转换图:

(1)进入区(Entrt Set):表示线程通过synchronized要求获取对象的锁。如果对象未被锁住,则迚入拥有者;否则则在进入区等待。一旦对象锁被其他线程释放,立即参与竞争。

(2)拥有者(The Owner):表示某一线程成功竞争到对象锁。

(3)等待区(Wait Set):表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒

        从图中可以看出,一个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在 “Wait Set”中等待的线程状态是 “in Object.wait()”

  • jstack -l pid 查看线程堆栈信息排查Java死锁
root@fam30-10-20-146-65 ~$ jstack -F -l 95865
Attaching to process ID 95865, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.162-b12
Deadlock Detection:

No deadlocks found.
  • jstack 分析CPU过高问题
(1)top 
查看占用cpu最高的进程pid
(2)top -Hp pid
查看该进程各个线程的cpu使用情况,定位到cpu占用最高的线程的pid
(3) jstack pid
使用jstack pid命令来查看当前java进程的堆栈状态, 将pid 从10进制转换成16进制,即可得到 
堆栈信息中的nid
(4) jstack -l [PID] >/tmp/log.txt
也可以把堆栈信息打印到一个文件里面进行分析。

2.4 jmap

        jmap 是 Java Development Kit(JDK)自带的一个工具,用于生成Java堆转储文件(Heap Dump)。它的设计目的是为了帮助开发人员分析和调试Java应用程序在运行时产生的内存问题

         jmap 命令可以通过连接到运行中的Java进程,生成指定类型的Java堆转储文件。Java堆转储文件是应用程序在特定时间点的内存快照,记录了Java虚拟机中对象的详细信息、引用关系以及当前内存使用情况

        jmap可以生成多种格式的Java堆转储文件,包括二进制格式(默认)、文本格式和HPROF二进制格式,它支持在特定的进程ID下生成堆转储文件,也支持对远程虚拟机进行操作。
同时,jmap还提供了一些额外的选项,如打印类加载器信息、查看共享对象映射等

2.4.1 jmap特点

优点:

提供了一种快速生成Java堆转储文件的方式,方便开发人员获取内存快照;
可以生成各种格式的堆转储文件,如二进制格式、文本格式等,满足不同需求;
在处理大型堆转储文件时,jmap 的性能表现较好。
缺点:

jmap 需要连接到正在运行的Java进程,如果权限不足或者连接失败,可能无法使用该命令;
处理大型堆转储文件时,可能会占用较多的系统资源,包括CPU和内存;
jmap 生成的堆转储文件可能过于庞大,需要额外的工具来分析和解读

2.4.2 语法格式

Usage:
    jmap [option] <pid>
        (to connect to running process)(连接到正在运行的进程)
    jmap [option] <executable <core>
        (to connect to a core file)(连接到核心文件)
    jmap [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server)(连接到远程调试服务器)

其中,[option] 是可选的一些命令参数选项,<pid> 是Java进程的进程ID。对于命令中可选参数汇总如下:

// <none> 
打印与Solaris pmap相同的信息 

// -heap 
打印Java堆摘要信息 

// -histo[:live] 
打印Java堆对象的直方图;如果指定"live"子选项,则只计算存活对象 

// -clstats 
打印类加载器统计信息 

// -finalizerinfo 
打印等待终止的对象信息 

// -dump:<dump-options> 
以hprof二进制格式导出Java堆。转储选项<dump-options>:
live:仅转储活动对象;如果没有指定,堆中的所有对象都被转储;
format=b 二进制格式;
file=<file> 将堆转储到<file> 。 
示例: 
jmap-dump:live,format=b,file=heap.bin<pid> 

// -F 
强制操作。与-dump:<dump-options> <pid>或 -histo一起使用,当不响应时强制执行堆转储或直方图。此模式不支持"live"子选项 

        如果生成的堆转储文件过大,可以考虑使用压缩格式(如gzip)进行存储,以减少其占用空间。对于大型堆转储文件,可以使用 jhat 或其他分析工具来进一步解读和分析。

  • 生成堆转储文件

jmap -dump:format=b,file=/home/dump.hprof pid
end
  • 作者:旭仔(联系作者)
  • 发表时间:2024-03-21 23:41
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 转载声明:如果是转载栈主转载的文章,请附上原文链接
  • 公众号转载:请在文末添加作者公众号二维码(公众号二维码见右边,欢迎关注)
  • 评论