主要查看堆内存中的一些信息,看看哪些类的实例对象占用了多大的堆内存
通过jmap -histo 进程id
命令查看堆中实例情况
文件内容如下
实例数 内存容量 类num #instances #bytes class name
----------------------------------------------1: 614068 142821992 [C2: 33839 109489600 [I3: 40356 18126952 [B4: 387655 9303720 java.lang.String5: 51323 4516424 java.lang.reflect.Method6: 62553 4003392 java.net.URL7: 46750 3351808 [Ljava.lang.Object;8: 96335 2077432 [Ljava.lang.Class;9: 64454 2062528 org.springframework.boot.loader.jar.StringSequence10: 21121 1687120 [S11: 50557 1617824 java.util.concurrent.ConcurrentHashMap$Node12: 19954 1607600 [Ljava.util.HashMap$Node;13: 61256 1470144 java.lang.StringBuffer14: 42761 1368352 java.util.HashMap$Node15: 55896 1341504 org.springframework.boot.loader.jar.JarURLConnection$JarEntryName16: 11999 1329664 java.lang.Class17: 31507 1260280 java.util.LinkedHashMap$Entry
......
通过jmap -heap 进程id
命令查看堆信息
C:\Users\Administrator>jmap -heap 9980
Attaching to process ID 9980, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.291-b10using thread-local object allocation.
Parallel GC with 15 thread(s)Heap Configuration:MinHeapFreeRatio = 0MaxHeapFreeRatio = 100MaxHeapSize = 8514437120 (8120.0MB)NewSize = 177209344 (169.0MB)MaxNewSize = 2837970944 (2706.5MB)OldSize = 355467264 (339.0MB)NewRatio = 2SurvivorRatio = 8MetaspaceSize = 21807104 (20.796875MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize = 17592186044415 MBG1HeapRegionSize = 0 (0.0MB)Heap Usage:
PS Young Generation
Eden Space:capacity = 734527488 (700.5MB)used = 93223176 (88.90454864501953MB)free = 641304312 (611.5954513549805MB)12.69158438901064% used
From Space:capacity = 13107200 (12.5MB)used = 0 (0.0MB)free = 13107200 (12.5MB)0.0% used
To Space:capacity = 15728640 (15.0MB)used = 0 (0.0MB)free = 15728640 (15.0MB)0.0% used
PS Old Generationcapacity = 425721856 (406.0MB)used = 30535456 (29.120880126953125MB)free = 395186400 (376.8791198730469MB)7.172630573141164% used28339 interned Strings occupying 3515304 bytes.
通过jmap -dump:format=b,file=导出文件路径 进程id
命令导出堆的dump文件
C:\Users\Administrator>jmap -dump:format=b,file=d:\eureka.dump 9980
Dumping heap to D:\eureka.dump ...
Heap dump file created
然后我们可以使用jvisualvm
工具来导入dump文件
我们一般会配置发生内存溢出时自动导出dump文件(内存很大时可能导出失败)
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./ (路径)
常用命令总结
# 查看历史生成的实例
jmap -histo 进程id# 查看当前存活的实例,执行过程中可能触发一次FullGC
jmap -histo:live 进程id# 查看堆内存中各个区域总内存、使用内存、剩余内存情况
jmap -heap 进程id# 导出dump文件
jmap -dump:format=b,file=导出文件路径 进程id
一般用来定位线程死锁问题、找出哪些线程占用cpu资源较高
案例:写一个死锁的程序
public class DeadLockTest {private static Object lock1 = new Object();private static Object lock2 = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (lock1) {try {System.out.println("thread1 begin");Thread.sleep(5000);} catch (InterruptedException e) {}synchronized (lock2) {System.out.println("thread1 end");}}}).start();new Thread(() -> {synchronized (lock2) {try {System.out.println("thread2 begin");Thread.sleep(5000);} catch (InterruptedException e) {}synchronized (lock1) {System.out.println("thread2 end");}}}).start();System.out.println("main thread end");}
}
接下来使用jstack 进程id
命令查看
C:\Users\Administrator>jstack 11548
输出结果如下
中间部分表示Thread-1线程再等待0x0000000716edac88
锁对象,现在自己占有了0x0000000716edac98
锁对象
另一部分截图:前一部分是发现了一个线程死锁,Thread-1在等待某个锁对象,这个锁对象目前被Thread-0占用,Thread-0在等待某个锁对象,这个锁对象目前被Thread-1占用。下面一部分就是指出了在哪个类哪个位置出现了死锁
还可以用jvisualvm自动检测死锁
案例,模拟占用cpu资源代码
package com.tuling.jvm;/*** 运行此代码,cpu会飙高*/
public class Math {public static final int initData = 666;public static User user = new User();public int compute() { //一个方法对应一块栈帧内存区域int a = 1;int b = 2;int c = (a + b) * 10;return c;}public static void main(String[] args) {Math math = new Math();while (true){math.compute();}}
}
首先使用top命令找到占用cpu较高的进程id
使用命令top -p 进程id
,显示你的java进程的内存情况
然后按 shift+h
键,查看该进程中的线程信息,找到占用cpu资源较高的线程
找到内存和cpu占用最高的线程tid,比如19664
将十进制转换为十六进制得到 0x4cd0,因为jstack命令中线程id是用十六进程显示的
使用jstack 进程id | grep -A 10 十六进程线程id
,得到线程堆栈信息中 4cd0 这个线程所在行的后面10行,从堆栈中可以发现导致cpu飙高的调用方法
一般生产环境是不会开放端口让我们远程连接jvisualvm的,了解即可
启动普通的jar程序JMX端口配置:
java -Dcom.sun.management.jmxremote.port=8888 -Djava.rmi.server.hostname=192.168.65.60 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -jar microservice-eureka-server.jar
-Dcom.sun.management.jmxremote.port 为远程机器的JMX端口
-Djava.rmi.server.hostname 为远程机器IP
tomcat的JMX配置:在catalina.sh文件里的最后一个JAVA_OPTS的赋值语句下一行增加如下配置行
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.port=8888 -Djava.rmi.server.hostname=192.168.50.60 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
连接时确认下端口是否通畅,可以临时关闭下防火墙
主要是查看当前JVM运行参数和系统参数
jinfo -flags 进程id
命令查看当前JVM运行参数
[root@VM-8-7-centos ~]# jps
14164 BrokerStartup
14088 NamesrvStartup
11897 Jps
26238 jar[root@VM-8-7-centos ~]# jinfo -flags 14088
Attaching to process ID 14088, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.201-b09
Non-default VM flags: -XX:CICompilerCount=2 -XX:+CMSClassUnloadingEnabled -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:CompressedClassSpaceSize=327155712 -XX:GCLogFileSize=31457280 -XX:InitialHeapSize=536870912 -XX:MaxHeapSize=536870912 -XX:MaxMetaspaceSize=335544320 -XX:MaxNewSize=268435456 -XX:MetaspaceSize=134217728 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=268435456 -XX:NumberOfGCLogFiles=5 -XX:OldPLABSize=16 -XX:OldSize=268435456 -XX:-OmitStackTraceInFastThrow -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SoftRefLRUPolicyMSPerMB=0 -XX:SurvivorRatio=8 -XX:+UseCMSCompactAtFullCollection -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseGCLogFileRotation -XX:-UseLargePages -XX:-UseParNewGC
Command line: -Xms512m -Xmx512m -Xmn256m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSClassUnloadingEnabled -XX:SurvivorRatio=8 -XX:-UseParNewGC -verbose:gc -Xloggc:/dev/shm/rmq_srv_gc_%p_%t.log -XX:+PrintGCDetails -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m -XX:-OmitStackTraceInFastThrow -XX:-UseLargePages -Djava.ext.dirs=/usr/local/java/jdk1.8.0_201/jre/lib/ext:/root/software/rocketMQ/rocketmq-all-4.9.0-bin-release/bin/../lib:/usr/local/java/jdk1.8.0_201/lib/ext
jinfo -sysprops 进程id
查看系统参数
[root@VM-8-7-centos ~]# jinfo -sysprops 14088
Attaching to process ID 14088, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.201-b09
java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.201-b09
sun.boot.library.path = /usr/local/java/jdk1.8.0_201/jre/lib/amd64
rocketmq.remoting.version = 393
java.vendor.url = http://java.oracle.com/
java.vm.vendor = Oracle Corporation
path.separator = :
file.encoding.pkg = sun.io
java.vm.name = Java HotSpot(TM) 64-Bit Server VM
sun.os.patch.level = unknown
sun.java.launcher = SUN_STANDARD
user.country = US
user.dir = /root/software/rocketMQ/rocketmq-all-4.9.0-bin-release
java.vm.specification.name = Java Virtual Machine Specification
java.runtime.version = 1.8.0_201-b09
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = /usr/local/java/jdk1.8.0_201/jre/lib/endorsed
java.io.tmpdir = /tmp
line.separator =
......
jstat命令可以查看堆内存各部分使用量、加载类数量等等
jstat [-选项] 进程id [间隔时间 毫秒] [查询次数]
# 评估程序内存使用以及GC压力整体情况
jstat -gc 进程id
jstat -gccapacity 进程id
jstat -gcnew 进程id
jstat -gcnewcapacity 进程id
jstat -gcold 进程id
jstat -gcoldcapacity 进程id
jstat -gcmetdcapacity 进程id
jstat -gcutil 进程id
总体优化思路:让每次MinorGC存活对象小于Survivor区容量的50%,将对象都留在年轻代中,尽量别进入老年代中,减少FullGC频率
查看当前JVM运行的参数
或者是直接使用gcinfo -flags
命令查看运行参数。从而了解到堆中各区域的内存容量,比如堆内存大小、年轻代大小、Eden区和Survivor区比例、老年代大小、大对象阀值、分代年龄阀值相关信息
使用jstat -gc 进程id 间隔时间 统计次数
命令统计Eden区对象增长速率
间隔时间使用每秒打印、每分钟打印都行,如果系统负载不高,可以把频率1秒换成1分钟
统计MinorGC触发频率、每次耗时
知道了Eden区容量大小、每秒Eden区增长多少对象,那么我们就可以估算出大概多久触发一次MinorGC了。耗时可以通过 YGCT/YGC 公式算出
根据结果我们大概就能知道系统大概多久会因为Young GC的执行而卡顿多久。
统计每次MinorGC后有多少对象存活进入Old区
知道了MinorGC的频率,假设5分钟一次,那么可以就可以jstat -gc 进程id 300000 10
观察每次Eden、Survivor、old使用的变化。
每次MinorGC后Eden区使用一般会大幅减少,Survivor区和Old区使用情况会增加,这些增加的对象就是存活的对象。进而我们就能大致估算出每次MinorGC后有多少对象会进入到老年代中,推算出老年代对象增长速率
统计FullGC触发频率与耗时
知道了老年代对象的增长速率就可以推算出Full GC的触发频率了,Full GC的每次耗时可以用公式 FGCT/FGC 计算得出。
目前我们就能画出类似于下面的一种模型图
接下来我们需要知道一般有哪些情况会触发FullGC
方法区元空间放满
可以使用jstat
命令按一定评率查看元空间使用过情况,然后排除这种可能性
显示调用System.gc()
可以使用-XX:+DisableExplicitGC
参数禁用,一般这种情况也很少出现
Old区容量使用率达到设定的阀值
老年代空间分配担保机制
大概率是进入老年代的对象过多导致FullGC。我们接下来需要知道哪些情况下对象会进入到老年代
对业务的熟悉程度我们先排查大对象的情况。
一般的对象都是朝生夕死的对象,再排查分代年龄达到阀值的情况。
比较大的可能性是对象动态年龄判断机制导致的,我们可以尝试增大年轻代内存容量,然后再进行测试。
调优,增加年轻代内存空间后,可能频繁产生FullGC的情况就不会出现了,也有可能还会更加频繁,这种情况的出现我们还是一样慢慢排查触发FullGC的各种情况,最大的可能性是老年代空间分配担保机制的原因,因为年轻代现在增大了,老年代减少了。
我们可以使用jmap命令查看一下当前堆中有没有哪个业务对象的实例很多,占用空间很多。如果找到了我们就去代码中找一下哪些地方创建了这个对象进而优化代码,如果创建对象的地方非常多,不太好定位问题那么我们可以尝试使用stack命令找一下占用cpu较多的线程,根据输出提示找到具体的java代码。一般创建较多的大对象也同时会占用较多的cpu资源,所以我们也可以从这方面定位到出现问题的代码进行优化