最近参加面试多次被面试官问到JVM
调 优方面的问题,即时自己面试前也重点复习了这一块的面试题,但是发现还是回答地不太好,浪费了好多次面试机会,真是让自己很抓狂。归根结底是自己以前一直只注重业务,而忽略了JVM
调优这一块,对JVM
这一块的实践太少了。这几天自己也重点观看了马士兵老师的JVM
调优视频课, 看完之后自己也在本机和腾讯云服务器上进行了一番实践,感觉还是很有收获的。
JDK
自带诊断工具命令我们知道JDK
安装目录的bin目录下有一系列的命令可以用来诊断和分析服务器CPU和内存占用过高的问题。JDK 安装目录的bin目录下主要有一下帮助我们进行JVM调优的命令和工具
常用JDK
性能分析命令
JDK自带的几款在线监控工具(jps、jstat、jstack、jmap),用户实时监控JVM运行装填
名称 | 主要作用 |
---|---|
jps | JVM Process Status Tool, 显示指定系统内所有 Hotspot 虚拟机进程 |
jstat | JVM Statics Monitoring Tool, 用于手机 Hotspot 各方面的运行数据 |
jinfo | Configuration Info for Java,显示虚拟机配置信息 |
jstack | Stack Trace for Java,显示虚拟机的线程快照 |
jmap | Memory Map for Java,生成虚拟机的内存转储快照(heapdump 文件) |
jhat | JVM Heap Analysis Tool,用于分析dump 文件(它会建立一个Http/HTML服务器,让用户可以在游览器上查看分析结果) |
其中 jhat
命令从JDK9
开始在JDK
的bin目录已经被移除,笔者下面就不对jhat
命令做过多介绍了。
使用场景 :查看当前机器的所有Java进程信息(可追踪到应用进程ID 、启动类名、文件路径。)
指令格式 : jps 【options 】 [hostid]
[hostid] 远程地址,可选参数,指定特定主机的IP或者域名,也可以指定具体协议端口,不指定则查看当前机器的相关信息,hostid所指机器必须开启jstatd服务
选项 | 作用 |
---|---|
-q | 只输出进程id |
-m | 输出虚拟机启动时传递给主类main函数的参数 |
-l | 输出主类的全名,如果进程执行的是jar包,则输出jar包路径 |
-v | 输出虚拟机进程启动时的JVM参数 |
在IDEA 中启动一个Java项目,然后点击下图所示的Terminal
图标按钮打开一个命令终端
1)我们下面所有执行 jvm
有关的命令都以这种方式进行,输入jsp
命令可以看到控制台中输出进程相关信息:
PS D:\giteeProjects\blogserver> jps
22464
21412 BlogserverApplication
21400 Jps
6280 Launcher
左边的数字代表进程ID, 右边的字符串表示进程main
函数的类
2)输入jps -l
:
PS D:\giteeProjects\blogserver> jps -l
22464
29232 sun.tools.jps.Jps
21412 org.sang.BlogserverApplication
6280 org.jetbrains.jps.cmdline.Launcher
左边的数字代表进程ID, 右边的字符串表示进程main
函数的类的全类名
3)输入 jps -v
PS D:\giteeProjects\blogserver> jps -v
22464 exit -XX:ErrorFile=C:\Users\xeawp\\java_error_in_idea64_%p.log -XX:HeapDumpPath=C:\Users\xeawp\\java_error_in_idea64.hprof -Xms128m -Xmx1024m -XX:ReservedCodeCac
heSize=512m -XX:+IgnoreUnrecognizedVMOptions -XX:+UseG1GC -XX:SoftRefLRUPolicyMSPerMB=50 -XX:CICompilerCount=2 -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFast
Throw -ea -Dsun.io.useCanonCaches=false -Djdk.http.auth.tunneling.disabledSchemes="" -Djdk.attach.allowAttachSelf=true -Djdk.module.illegalAccess.silent=true -Dkotlinx.
coroutines.debug=off -XX:ErrorFile=$USER_HOME/java_error_in_idea_%p.log -XX:HeapDumpPath=$USER_HOME/java_error_in_idea.hprof --add-opens=java.base/jdk.internal.org.obje
ctweb.asm=ALL-UNNAMED --add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED -javaagent:D:\jetbrain_tools\jetbra\ja-netfilter.jar=jetbrains -Djb.vmOption
sFile=D:\jetbrain_tools\jetbra\vmoptions\idea.vmoptions -Djava.system.class.loader=com.intellij.util.lang.PathClassLoader -Didea.vendor.name=JetBrains -Didea.paths.selector=IntelliJIdea2023.2
13268 Jps -Dapplication.home=C:\Program Files\Java\jdk1.8.0_191 -Xms8m
21412 BlogserverApplication -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:55645,suspend=y,server=n -Xms50m -Xmx50m -XX:TieredStopAtLevel=1 -Xverify:none -Dspring.profiles.active=dev -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dsp
ring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dmanagement.endpoints.jmx.exposure.include=* -javaagent:C:\Users\xeawp\AppData\Local\JetBrains\IntelliJIdea2023.2\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8
6280 Launcher -Xmx700m -Djava.awt.headless=true -Djna.boot.library.path=D:\Program Files\JetBrains\IntelliJ IDEA 2023.2.2/lib/jna/amd64 -Djna.nosys=true -Djna.noclasspa
th=true --add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tool
s.javac.code=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-opens=jdk.com
piler/com.sun.tools.javac.main=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAME
D --add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.too
ls.javac.jvm=ALL-UNNAMED -Dpreload.project.path=D:/giteeProjects/blogserver -Dpreload.config.path=C:/Users/xeawp/AppData/Roaming/JetBrains/IntelliJIdea2023.2/options -Dexternal.project.config=C:\Use
每行左边的数字代表进程ID,右边的文本表示该进程启动时的JVM参数, 以-XX:
开头的参数都代表JVM启动参数
jstat
命令可以查看Java程序运行时相关信息,可以通过它查看堆信息的相关情况
jstat
命令的格式:jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
options 参数表示可选项; vmid
参数表示进程ID;interval 参数表示统计间隔时间,单位为秒或者毫秒,默认为毫秒;count 参数表示统计次数。
options 参数由下列可选值构成
选项 | 作用 |
---|---|
-class | 显示类加载、卸载数量、总空间及类装载所耗费的时间 |
-gc | 监视 java 堆状况,包括 Eden区、Survivor区、老年代、永久代等的容量、已使用空间、垃圾收集时间等信息 |
-gccapacity | 监视内容基本与-gc 相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间 |
-gcutil | 监视内容基本与-gc 相同,但输出主要关注已使用空间占总空间的百分比 |
-gccause | 与 -gcutil 功能一样,但会额外输出导致上一次垃圾收集产生的原因 |
-gcnew | 监视新生代垃圾收集状况 |
-gcnewcapacity | 监视内容与 -gcnew 基本相同,输出主要关注使用到的最大、最小空间 |
-gcold | 监视老年队垃圾收集状况 |
-gcoldcapacity | 监视内容与 -gcold 基本相同,输出主要关注使用到的最大、最小空间 |
-gcpermcapacity | 输出永久代使用的最大、最小空间 |
-complier | 输出即时编译器编译过的方法、耗时等 |
-printcompilation | 输出已经被即时编译的方法 |
1)获取21412进程的 GC 统计信息:
PS D:\giteeProjects\blogserver> jstat -gc 21412
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
3072.0 3072.0 766.6 0.0 10752.0 4459.4 34304.0 27245.3 53160.0 49412.8 7336.0 6683.0 68 0.100 2 0.085 0.185
参数说明:
2)每间隔一段时间获取固定次数的GC统计信息:
PS D:\giteeProjects\blogserver> jstat -gc 21412 1000 5
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
3072.0 3072.0 766.6 0.0 10752.0 5676.0 34304.0 27245.3 53160.0 49412.8 7336.0 6683.0 68 0.100 2 0.085 0.185
3072.0 3072.0 766.6 0.0 10752.0 5676.0 34304.0 27245.3 53160.0 49412.8 7336.0 6683.0 68 0.100 2 0.085 0.185
3072.0 3072.0 766.6 0.0 10752.0 5676.0 34304.0 27245.3 53160.0 49412.8 7336.0 6683.0 68 0.100 2 0.085 0.185
3072.0 3072.0 766.6 0.0 10752.0 5676.0 34304.0 27245.3 53160.0 49412.8 7336.0 6683.0 68 0.100 2 0.085 0.185
3072.0 3072.0 766.6 0.0 10752.0 5676.0 34304.0 27245.3 53160.0 49412.8 7336.0 6683.0 68 0.100 2 0.085 0.185
这里由于笔者本地的21412
进程服务没有使用,所以每次垃圾回收后各个区的内存使用没有发生什么变化,而一个线上不断有用户使用的服务进程通过这个命令是可以看到明显的拉进回收堆内存变化的。
3)获取21412 进程垃圾回收后各个区已使用内存占其对应区的总内存大小百分比数据:
PS D:\giteeProjects\blogserver> jstat -gcutil 21412
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
24.95 0.00 55.81 79.42 92.95 91.10 68 0.100 2 0.085 0.185
这里的S0、S1、E、O、M等参数对应的值为各个区对应的已使用内存大小占它们各自总内存大小的百分比。
这个命令主要用来查看和修改JVM参数
jinfo命令格式:jinfo [option]
option 为选项参数;pid 代表进程id
option 有以下这些选项参数
1)获取16462 进程 jvm 的全部参数和系统属性:
PS D:\giteeProjects\blogserver> jinfo 21412
Attaching to process ID 21412, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.191-b12
Java System Properties:
spring.output.ansi.enabled = always
java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.191-b12
sun.boot.library.path = C:\Program Files\Java\jdk1.8.0_191\jre\bin
java.vendor.url = http://java.oracle.com/
java.vm.vendor = Oracle Corporation
path.separator = ;
java.rmi.server.randomIDs = true
file.encoding.pkg = sun.io
java.vm.name = Java HotSpot(TM) 64-Bit Server VM
sun.os.patch.level =
sun.java.launcher = SUN_STANDARD
user.script =
user.country = CN
management.endpoints.jmx.exposure.include = *
user.dir = D:\giteeProjects\blogserver
java.vm.specification.name = Java Virtual Machine Specification
PID = 21412
intellij.debug.agent = true
java.runtime.version = 1.8.0_191-b12
java.awt.graphicsenv = sun.awt.Win32GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = C:\Program Files\Java\jdk1.8.0_191\jre\lib\endorsed
spring.profiles.active = dev
line.separator =java.io.tmpdir = C:\Users\xeawp\AppData\Local\Temp\java.vm.specification.vendor = Oracle Corporation
user.variant =
os.name = Windows 10
sun.jnu.encoding = GBK
VM Flags:
Non-default VM flags: -XX:-BytecodeVerificationLocal -XX:-BytecodeVerificationRemote -XX:CICompilerCount=12 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=52428800 -XX:+ManagementServer -XX:MaxHeapSize=52428800 -XX:MaxNewSize=17301504 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=17301504 -XX:OldSize=35127296 -XX:TieredStopAtLevel=1 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line: -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:55645,suspend=y,server=n -Xms50m -Xmx50m -XX:TieredStopAtLevel=1 -Xverify:none -Dspring.profiles.active=dev -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dmanagement.endpoints.jmx.exposure.include=* -javaagent:C:\Users\xeawp\AppData\Local\JetBrains\IntelliJIdea2023.2\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8
2)获取新生代大小 JVM 参数值
PS D:\giteeProjects\blogserver> jinfo -flag NewSize 21412
-XX:NewSize=17301504 #单位为byte
3)开启GC打印日志
PS D:\giteeProjects\blogserver> jinfo -flag +PrintGCDetails 21412
4)开启堆内存溢出日志打印(默认是关闭的)
PS D:\giteeProjects\blogserver> jinfo -flag +HeapDumpOnOutOfMemoryError 21412
5)设置堆内存溢出时的堆转储文件路径
PS D:\giteeProjects\blogserver> jinfo -flag :HeapDumpPath=D:\heapdump\blogDump.hprof
6)修改当堆内存对象所占空间超过80%时进行扩容
PS D:\giteeProjects\blogserver> jinfo -flag MaxHeapFreeRatio=80 21412
7)最后我们再来使用jinfo -flags PID
命令来查看21412进程的JVM参数信息
PS D:\giteeProjects\blogserver> jinfo -flags 21412
Attaching to process ID 21412, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.191-b12
Non-default VM flags: -XX:-BytecodeVerificationLocal -XX:-BytecodeVerificationRemote -XX:CICompilerCount=12 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:In
itialHeapSize=52428800 -XX:+ManagementServer -XX:MaxHeapFreeRatio=80 -XX:MaxHeapSize=52428800 -XX:MaxNewSize=17301504 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=17301504
-XX:OldSize=35127296 -XX:+PrintGCDetails -XX:TieredStopAtLevel=1 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line: -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:55645,suspend=y,server=n -Xms50m -Xmx50m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\heapdum
p\blogDump.hprof -XX:TieredStopAtLevel=1 -Xverify:none -Dspring.profiles.active=dev -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enab
led=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dmanagement.endpoints.jmx.exposure.include=* -javaagent:C:\Users\xeawp\AppData\Local\JetBrains\IntelliJIdea2023.2\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8
可以看到明显多了-XX:MaxHeapFreeRatio=80
、-XX:+PrintGCDetails
和 -XX:HeapDumpPath=D:\heapdum p\blogDump.hprof
等 JVM
启动参数值。
使用场景:查看JVM线程信息和生成线程快照
jstack 命令格式:jstack [ 选项 ] [进程ID] 远程IP
选项 | 作用 |
---|---|
-F | 当正常输出的请求不被响应时,强制输出线程堆栈 |
-l | 除堆栈外,显示关于锁的附加信息 |
-m | 如果调用了本地方法的话,可以显示C/C++的堆栈 |
1)在21412进程的项目源码中写一个线程死锁的接口
@RestController
@RequestMapping("/jvm")
public class JVMTestController {
Object lock1 = new Object();
Object lock2 = new Object();
Thread thread1 = new Thread(() -> {
while (true){
synchronized (lock1){
System.out.println("thread1 get lock1");
synchronized (lock2){
System.out.println("thread1 get lock2");
}
}
}
}, "thread1");
Thread thread2 = new Thread(()->{
while (true){
synchronized (lock2){
System.out.println("thread2 get lock2");
synchronized (lock1){
System.out.println("thread2 get lock1");
}
}
}
}, "thread2");
thread1.start();
thread2.start();
RespBean<String> respBean = new RespBean<>();
respBean.setStatus(200);
respBean.setMsg("success");
respBean.setData("启动了两个线程");
return respBean;
}
1)然后重启服务(服务重启后服务进程ID会发生改变)并在ApiPost中先调用登录接口获取token认证信息(笔者这里是在服务启动之前就已经有这段代码了,所以进程ID没有发生变化)
3)在ApiPost中新建一个测试死锁的http接口,并将登录接口返回的authToken
字段值放到测试死锁接口的Authorization
请求头参数中
在日志控制台中可以看到 thread1
和 thread2
都只拿到了一把锁
thread1 get lock1
thread2 get lock2
4)在Terminal
控制台中输入 jstack -l 21412
可以看到 thread1
和 thread2
两个线程都出现了阻塞
PS D:\giteeProjects\blogserver> jstack -l 21412
2023-12-13 13:21:13
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):
"thread2" #64 daemon prio=5 os_prio=0 tid=0x000000001c708000 nid=0x7e94 waiting for monitor entry [0x000000001aa8f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.sang.controller.JVMTestController.lambda$deadLockTest$1(JVMTestController.java:61)
- waiting to lock <0x00000000ff16b240> (a java.lang.Object)
- locked <0x00000000ff16b250> (a java.lang.Object)
at org.sang.controller.JVMTestController$$Lambda$1155/562657671.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"thread1" #63 daemon prio=5 os_prio=0 tid=0x000000001c705000 nid=0x5968 waiting for monitor entry [0x000000000173f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.sang.controller.JVMTestController.lambda$deadLockTest$0(JVMTestController.java:49)
- waiting to lock <0x00000000ff16b250> (a java.lang.Object)
- locked <0x00000000ff16b240> (a java.lang.Object)
at org.sang.controller.JVMTestController$$Lambda$1154/754784134.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"DestroyJavaVM" #57 prio=5 os_prio=0 tid=0x000000001c709800 nid=0xf80 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
5)在Terminal
控制台中输入jstack -F 21412
回车后可以看到具体的线程死锁信息
PS D:\giteeProjects\blogserver> jstack -F 21412
Attaching to process ID 21412, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.191-b12
Deadlock Detection:
Found one Java-level deadlock:
=============================
"thread1":
waiting to lock Monitor@0x00000000197efc68 (Object@0x00000000ff16b250, a java/lang/Object),
which is held by "thread2"
"thread2":
waiting to lock Monitor@0x00000000197ed538 (Object@0x00000000ff16b240, a java/lang/Object),
which is held by "thread1"
Found a total of 1 deadlock.
可以看到thread1
线程等待获取thread2
持有的锁,而thread2
线程也在等待获取thread1
持有的锁,但是它们彼此都没有释放自己持有的锁 ,于是造成了死锁。
jmap 命令是JVM
内存信息监控和 Java 内存映像工具
使用场景:监控堆内存使用情况和对象占用情况, 生成堆内存快照文件,查看堆内存区域配置信息。
jmp 命令格式:jmap [option]
option选项值如下:
选项 | 作用 |
---|---|
-dump | 生成java堆转储快照,格式为dump:[live,]format=b,file=,其中live子参数指定是否只dump出存活对象。 |
-finalizerinfo | 显示在F-Queue中等待Finerlizer线程执行 finalize方法的对象,只在linux/Solaries平台下有效 |
-heap | 显示java堆详细信息,包括使用哪种垃圾收集器、参数配置、分代状况等,只在linux/Solaries平台下有效 |
-permstat | 以ClassLoader 为统计口径显示永久代内存状态,只在linux/Solaries平台下有效 |
-histo | 显示堆中对象统计信息,包括类、实例数量、合计容量 |
-F | 当虚拟机进程对 -dump 选项没有响应时,可使用这个选项强制生成dump快照,只在linux/Solaries平台下有效 |
1)在终端命令控制台中输入jmap -heap 21412
查看堆详细信息
PS D:\giteeProjects\blogserver> jmap -heap 21412
Attaching to process ID 21412, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.191-b12
using thread-local object allocation.
Parallel GC with 13 thread(s) #使用并行垃圾收集器收集垃圾,启动了13个垃圾收集线程
Heap Configuration:
MinHeapFreeRatio = 0 #JVM堆缩减空间比率,低于则进行内存缩减
MaxHeapFreeRatio = 80 #JVM堆扩大内存空闲比例,高于则进行内存扩张
MaxHeapSize = 52428800 (50.0MB) #堆最大内存大小
NewSize = 17301504 (16.5MB) #新生代初始化内存大小
MaxNewSize = 17301504 (16.5MB) #新生代最大内存大小
OldSize = 35127296 (33.5MB) #老年代内存大小
NewRatio = 2 #新生代和老年代占堆内存比率
SurvivorRatio = 8 #survivor区和Eden区占新生代内存比率
MetaspaceSize = 21807104 (20.796875MB) #元数据初始化空间大小
CompressedClassSpaceSize = 1073741824 (1024.0MB) #类指针压缩空间大小
MaxMetaspaceSize = 17592186044415 MB #元数据最大内存大小
G1HeapRegionSize = 0 (0.0MB) #G1收集器Region单元大小
Heap Usage:
PS Young Generation
Eden Space: #Eden区
capacity = 7864320 (7.5MB) #Eden区总容量
used = 1097912 (1.0470504760742188MB) #Eden区已使用容量
free = 6766408 (6.452949523925781MB) #Eden区剩余容量
13.960673014322916% used #Eden区使用比例
From Space: #From区(也就是Survivor中的S1区)
capacity = 4718592 (4.5MB) #S1区总容量大小
used = 1014520 (0.9675216674804688MB) #S1区已使用大小
free = 3704072 (3.5324783325195312MB) #S1区剩余大小
21.50048149956597% used #S1使用比例
To Space: #To区 (也就是Survivor中的S2区)
capacity = 4718592 (4.5MB) #S2区总容量大小
used = 0 (0.0MB) #S2区已使用大小
free = 4718592 (4.5MB) #S2区剩余大小
0.0% used #/S2区使用比率
PS Old Generation #老年代
capacity = 35127296 (33.5MB) #老年代总容量大小
used = 22521464 (21.47814178466797MB) #老年代已使用大小
free = 12605832 (12.021858215332031MB) #老年代剩余大小
64.11385607363573% used #老年代使用功能比例
2)在终端命令控制台中输入jmap -histo <Pid>
获取堆中对象统计信息
PS D:\giteeProjects\blogserver> jmap -histo 21412
num #instances #bytes class name
----------------------------------------------
1: 68887 7308488 [C
2: 9412 2218296 [B
3: 17735 1560680 java.lang.reflect.Method
4: 62646 1503504 java.lang.String
5: 11883 1313912 java.lang.Class
6: 35207 1126624 java.util.concurrent.ConcurrentHashMap$Node
7: 15513 817600 [Ljava.lang.Object;
8: 5887 671776 [I
9: 16506 660240 java.util.LinkedHashMap$Entry
10: 7365 601184 [Ljava.util.HashMap$Node;
11: 15330 490560 java.util.HashMap$Node
12: 7616 426496 java.util.LinkedHashMap
13: 223 391552 [Ljava.util.concurrent.ConcurrentHashMap$Node;
14: 8620 275840 java.util.concurrent.locks.AbstractQueuedSynchronizer$Node
15: 12060 273648 [Ljava.lang.Class;
16: 16972 271552 java.lang.Object
17: 7105 227360 java.util.ArrayList$Itr
18: 4525 217200 java.util.HashMap
19: 8529 204696 java.util.ArrayList
20: 7490 179760 org.springframework.core.MethodClassKey
# 其他省略
num
: 序号,数字越小占用内存越大;#instances
: 类实例数;class name
:全类名
以上为21412进程中最占用堆内存的前20个类。
3)在终端命令控制台中输入jmap -dump:live,format=b,file=<filename> <PID>
输出堆转储文件
例如笔者在自己的云服务器上对nacos
服务进程4461堆转储操作
[root@VM-8-16-centos local]# jmap -dump:live,format=b,file=nacosDump.hprof 4461
Heap dump file created
然后把这个堆转储文件从云服务器上下载到本地磁盘
选中文件后鼠标右键->下载(默认的下载目录为:C:\Users${username}\Desktop\fsdownload)
jvisualvm
是JDK自带的具有图形界面操作功能的JVM
性能监控和诊断工具,它不仅能分析和诊断堆转储文件,在线实时监控本地JVM
进程,还能监控远程服务器上的JVM
进程。
1)在我们在安装JDK的bin目录双击jvisaulvm.exe
程序,程序启动成功后会进入Java VisualVM
界面
2)依次点击 文件-> 装入-> 选择文件名和文件类型,文件类型选择【堆Dump】导入我们从服务器上下载的dump文件
3)然后点击【打开】后 Java VisualVM
UI 的右边主界面会新增一个Tab页显示导入的dump文件的概要信息
4)点击下面的【显示线程】链接可以看到线程信息
5)点击【概要】右边的【类】可以看到主要占内存的类名、实例数以及占内存大小
可以看到这个堆中占内存前三位的分别是byte[]
、String
和 ConcurrentHashMap$Node
三个类
注意:生产环境一般不能使用jmap -dump
命令生成堆转储文件,因为执行这个命令的时候会导致STW
(应用主线程停顿),影响应用的正常使用。而一般通过设置两个jvm
启动参数:-XX:+HeapDumpOnOutOfMemoryError
和 -XX:HeapDumpPath=<filename>
在堆内存溢出时实现自动生成堆转储文件。然后我们从服务器上下载堆转储文件后通过Java VisualVM
或者Eclipse Memory Analyzer
内存分析工具导入我们下载的dump文件进行分析诊断。
首先需要我们设置JVM启动参数测试堆内存溢出时自动导出堆转储文件
1)在我们的本地启动的Java服务中通过IDEA
设置启动类参数
四个JVM启动参数分别如下:
-Xms50m
-Xmx50m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=D:\heapdump\blogDump.hprof
然后点击下面的【ok】使之生效
2)在JVMTestController
类中新增一个能导致内存溢出的接口
@GetMapping("/testOutMemoryError")
public RespBean<List<UserDTO>> outMemoryErrorTest(){
List<UserDTO> userDTOList = new ArrayList<>();
for(int i=0;i<1000000;i++){
UserDTO userDTO = new UserDTO();
userDTO.setId(i+1);
userDTO.setUsername("username"+i);
userDTO.setNickname("张三"+i);
userDTO.setPhoneNum(18975623568L);
userDTO.setEmail("ZhangSan"+i+"@163.com");
userDTO.setEnabled(1);
userDTO.setRegTime(new Timestamp(System.currentTimeMillis()));
userDTO.setUpdateTime(new Timestamp(System.currentTimeMillis()));
userDTOList.add(userDTO);
}
RespBean<List<UserDTO>> respBean = new RespBean<>();
respBean.setStatus(200);
respBean.setMsg("success");
respBean.setData(userDTOList);
return respBean;
}
3)重启服务后在ApiPost
中新建一个http接口,并在请求头中带上用户登录成功后返回的authToken
认证信息
点击【发送按钮】一段时间后会返回内存溢出异常信息,同时在D:\heapdump
目录下生成了堆转储文件blogDump.hprof
4)我们同样在 Java VisualVM
中导入这个dump
文件可以看到它的概要信息和类实例信息
内存溢出堆转储概要信息
内存溢出堆转储类实例信息
从内存溢出堆转储实例信息中我们可以看到UserDTO
类产生了47899个实例对象占用了4406708字节的堆内存。
本地监控很简单,打开VisualVM
就可以从左侧栏目里看到本机的应用,点击对应的应用就可以看到对应的内存、线程、GC信息
如果应用出现线程死锁也能通过【线程】tab页看到
点击右上方的【线程 Dump】可以看到线程死锁的具体原因
VisualVM
不仅能监控本地的应用程序,还可以监控远程服务器上的应用,虽然远程监控一般不会用于生产环境的,但是在测试环境做一些压力测试做一些性能的预调优,这个时候使用VisualVM来远程监控测试服务器的JVM使用情况,这样有助于我们了解到JVM
的实时运行状态而进行优化和调整。
1)应用配置jmx支持
需要使用VisualVM监控某个远程服务器的JVM应用,那么首先要对需要配置远程监控应用对MX的支持
配置方式:
catalina.sh
配置文件增加JAVA_OPTS配置jar 包启动配置案例:
nohup java -Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8999
-Dcom.sun.management.jmxremote.local.only=true
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.rmi.port=8999
-Dcom.sun.management.jmxremote.access.file=jmx.access
-Dcom.sun.management.jmxremote.password.file=jmx.password
-Djava.rmi.server.hostname=159.138.47.89
-jar jstx-server.jar&
需要注意的参数:
Dcom.sun.management.jmxremote.port
:指定jms通讯端口,这个随意只要不与其他应用冲突即可Djava.rmi.server.hostname
:连接IP,填写当前服务器的外网IPDcom.sun.management.jmxremote.ssl
:是否位加密连接Dcom.sun.management.jmxremote.authenticate
:是否进行权限连接认证,flase 不需要,ture的话就需要指定用户名,密码配置。management.jmxremote.access.file
: 用户名 和对应用户的权限配置Dcom.sun.management.jmxremote.password.file
:用户名对应的密码Dcom.sun.management.jmxremote.authenticate
配置为true则需要进行此步骤编辑jmx.access
文件添加用户名 并指定权限
执行命令:vim jmx.access
添加内容:admin readwrite
jmx.password
文件里指定用户名和对应密码
执行命令:vim jmx.access
添加内容:admin 123456
修改password,access文件访问权限:
Chmod –R 600 jmx.password
Chmod –R 600 jmx.access
2)建立内网公网IP映射
如果是云服务器则需要建立局域网与公网IP映射
vim /etc/hosts
3)开放端口访问限制
Dcom.sun.management.jmxremote.port
对应端口Dcom.sun.management.jmxremote.port
对应的端口4)VisualVM进行远程连接
添加远程服务器:
建立JMX
连接:
连接成功:
远程jmx连接成功之后就可以像监视本地JVM
进程一样监视远程服务器上的JVM
进程了。
不过生产环境远程服务器上的JMV
进程指标的监控大多使用阿里开源的Arthas
中间件, 这里限于文章篇幅笔者改天再新写一篇文章来介绍如何使用Arthas
中间件进行线上JVM
调优。
本文主要对JDK自带的JVM诊断和调优命令和工具的用法做了详细的介绍,包括jps
、jstat
、jinfo
、jstack
、jmap
等五个命令及Java VisualVM
图形话界面的具体使用。总结一下它们各自的用处与使用场景:
jps
: 查询 JVM 进程及其对应的启动类信息,主要用法:jps
或 jps -l
;jstat
:可以查看Java程序运行时相关信息,也可以通过它查看堆信息的相关情况,尤其可以监视垃圾回收信息,主要用法:jstat -gc <PID> [interval] [times]
jinfo
: 获取JVM
参数信息、修改JVM
参数值或使 JVM
参数生效或失效,主要用法:jinfo -flag <name> <PID>
、jinfo -flags <PID>
、jinfo -flag <name>=<value> <PID>
、jinfo -flag +<name> <PID>
等;jstack
:查看JVM
线程信息及生成线程快照,主要用法:jstack -l <PID>
、jstack -F
等;jmap
:监控堆内存使用情况和对象占用情况, 生成堆内存快照文件,查看堆内存区域配置信息,主要用法:jmap -histo <PID>
、jmap -dump:live,format=b,file=<filename> <PID>
,注意切忌在生产环境使用jmap -dump
导致应用长时间停顿的操作。Java VisualVM
:图形化界面应用可用来装入dump
文件进行诊断分析以及实时监控本地和远程JVM
的内存使用、线程和GC信息。