大纲
-
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').log | GC日志文件路径。需保证Log文件所在容器路径已存在,建议您将该容器路径挂载到NAS目录,以便自动创建目录以及实现日志的持久化存储。 |
-XX:+HeapDumpOnOutOfMemoryError | JVM发生OOM时,自动生成DUMP文件。 |
-XX:HeapDumpPath=/home/admin/nas/dump-${POD_IP}-$(date '+%s').hprof | DUMP文件路径。需保证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 GB | 600 MB |
2 GB | 1434 MB |
4 GB | 2867 MB |
8 GB | 5734 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
评论