JVM调优
- 主要就是对JVM内存大小的分配设置,内存收集器的选择和参数调节配置,来达到控制GC次数和GC时间,提高应用响应时间和吞吐量的目的;
JVM监控
-
JVM监测工具主要有:jps,jstat,jinfo,jmap,jhat,jconsole,virtualVM,linux可能需要进入bin目录,用./执行;
-
jps,jinfo用来分析启动参数,jstat,jmap用来分析实时情况,jconsole,virtualVM用来可视化分析;
JPS
- jps -l -v 用来查看所有jvm进程,会展示” pid jar包 jvm参数“等信息,可以很方便用来查看应用启动情况,这也可以非常方便检查应用启动脚本是否生效;
JSTAT
- jstat -class pid 用来查看当前pid进程的类加载情况;
加载11975个类,占据21KB
Loaded Bytes Unloaded Bytes Time
11975 21647.2 0 0.0 8.99
- jstat -gcutil pid 用来查看jvm内存使用情况(百分比);
S0使用0%,S1使用34%,E使用32%,O使用31%,M使用95%,YGC 19次,总共0.21s,FGC3次,总共0.5s,gc总共0.75s
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 34.04 32.72 31.37 95.65 93.51 19 0.210 3 0.548 0.758
-
jstat -gc pid 用来查看jvm内存使用情况,真实具体值;
-
还要更多参数配置,具体参考:https://www.cnblogs.com/yjd_hycf_space/p/7755633.html
JINFO
- jinfo pid 用来查看实际jvm启动参数,可以用来验证启动脚本是否生效,具体参考:https://www.jianshu.com/p/8d8aef212b25
JMAP
-
jmap -heap pid,查看堆使用情况;
-
jmap -histo 29620 > log.txt,查看pid为29620的应用,将堆内存中对象实例的数量和大小数据导出到log.txt中;
-
jmap -dump:format=b,file=heapdump.hprof 19287,导出当前堆内存镜像的dump文件,dump文件可以用virtualvm,mat等工具来分析,该命令会暂停应用,生产环境慎用;
VirtualVM和MAT
-
virtualVm可以用来分析dump文件,或者通过jmx连接远程应用来实时分析内存使用情况;
-
mat,也是一种堆dump文件的分析工具,可以查看实例大小,数目,所占空间(深堆,浅堆);
JSTACK
- jstack (-F) pid打印当前进程的所有线程堆栈信息,等价于Thread.getAllStackTraces(),用来检测死锁和检测线程耗用cpu过高的问题。
JHAT
- jhat也可用来分析dump文件,生成html网页分析结果
JVM监控示例
- 查看当前linux机器环境
top -c
Cpu(s): 6.9%us, 1.5%sy, 0.0%ni, 91.5%id, 0.0%wa, 0.0%hi, 0.1%si, 0.0%st
Mem: 8062128k total, 7820904k used, 241224k free, 151844k buffers
// 通过top命令来查看机器的cpu和内存使用率,91.5id,表示CPU未使用率。内存241M的空闲,另外可以查看到进程的cpu和内存使用情况
df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/vg_root-lv_root 78G 74G 16M 100% /
tmpfs 3.9G 0 3.9G 0% /dev/shm
/dev/sda1 477M 40M 413M 9% /boot
/dev/mapper/vg_root-lv_home 13G 203M 12G 2% /home
- 查看当前应用的内存分配设置
jps -l -v
15143 cu-ibas-pay-provider.jar -Dlog4j.path=/app/applogs/ibasPre
ps -ef|grep cu-ibas-pay-provider.jar
qzd 15143 1 0 Jan20 ? 00:25:07 java -jar -Dlog4j.path=/app/applogs/ibasPre cu-ibas-pay-provider.jar -Xms768m -Xmx768m -Xss256K -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
// 通过jps发现启动只有一个参数设置,进一步通过ps发现,启动命令有问题,cu-ibas-pay-provider.jar 之后全部不生效,通过修改发现生效
jinfo 15143
Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=130023424 -XX:MaxHeapSize=2065694720 -XX:MaxNewSize=688390144 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=42991616 -XX:OldSize=87031808 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -Dlog4j.path=/app/applogs/ibasPre
// 通过jps,发现内存设置是初始堆124M,最大堆1.9G,新生代最大656M,初始新生代41M,老年代83M,使用的ParallelGC
jstat -class 15143
Loaded Bytes Unloaded Bytes Time
19617 36120.2 0 0.0 162.67
// 通过jstat,发现162ms,载入19617个类,共36M
- 实时监控应用的内存,GC情况
jstat -gc 15143
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
11264.0 16384.0 10857.0 0.0 411648.0 133948.3 185856.0 75930.1 112920.0 108128.9 14104.0 13258.3 38 27.725 4 1.045 28.770
// 通过jstat,发现当前 S0分配11M,使用10M,S1分配16M,使用0,Eden分配411M,使用133M,Old分配185M,使用75M,Metaspace分配112M,使用108M,minorGC 38次,fullGC4次,
jmap -heap 15143
// 比jstat更直观,但没有gc信息
- springboot应用的内存监控actuator
- 堆dump文件分析
jmap -dump:format=b,file=[filename][pid],gceasy(在线https://gceasy.io/),GCViewer
GC日志分析
- GC日志需要在应用的启动参数中指定,如指定GC日志生成目录,指定GC日志生成格式,指定堆溢出时生成当前dump文件
// 设置在应用日志中输出gc详细信息
-verbose:gc
// 设置日志中打印GC详情,和-verbose:gc类似
-XX:+PrintGCDetails
// 设置日志中打印GC时间戳
-XX:+PrintGCTimeStamps
// 设置单独生成gc日志文件
-Xloggc:[file]
// 设置堆溢出时导出dump
--XX:+HeapDumpOnOutOfMemoryError
// 设置堆dump溢出文件路径
-XX:HeapDumpPath=/applogs/pre-yg-web_heapdump
// 设置jvm崩溃时的日志文件路径
-XX:ErrorFile=[file]
- GC日志中的关注点:停顿时间和吞吐量占比,如:
// 新生代无法分配内存空间,触发Minorgc,新生代从31744K回收到2647K,新生代总空间36844K,整个堆从31744K回收到2655K,堆的总大小121856K,耗
// 时;
0.448: [GC (Allocation Failure) [PSYoungGen: 31744K->2647K(36864K)] 31744K->2655K(121856K), 0.0086293 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
1.938: [GC (Metadata GC Threshold) [PSYoungGen: 210895K->3950K(259072K)] 214127K->8630K(344064K), 0.0073356 secs] [Times: user=0.02 sys=0.01, real=0.01 secs]
// Metadata元数据区达到阈值,触发FullGC,新生代从3959K回收到0K,总大小259072K,老年代从4679K会受到8046K,堆总大小从8630K回收到8046K,堆
// 总大小314880K,元数据区从20699K会受到20699K,元数据区总大小1067008K。
1.946: [Full GC (Metadata GC Threshold) [PSYoungGen: 3950K->0K(259072K)] [ParOldGen: 4679K->8046K(55808K)] 8630K->8046K(314880K), [Metaspace: 20699K->20699K(1067008K)], 0.0481115 secs] [Times: user=0.14 sys=0.00, real=0.05 secs]
常见问题
-
JVM内存泄露的常见原因:
大的static对象,static对象只有在FullGC才会清理,并且类还在使用时就不会回收,造成内存一直占用;
频繁的String的+操作,因为会频繁创建新的String对象,所以可以使用StringBuilder或StringBuffer代替,不过新版jdk已经有优化了;
循环中创建对象,递归层次深也可能造成内存泄露,尽量在循环外创建,进行对象复用,使用对象池,控制对象作用域,不要有太大的方法。 -
java应用的启动脚本
java -jar -Dlog4j.path=/applogs pre-web.jar & -Xloggc:/applogs/pre-web_gc.log
// 上面这样写是绝对错误的,会导致后面的设置不生效,需要改为
java -jar -Dlog4j.path=/applogs -Xloggc:/applogs/pre-web_gc.log pre-web.jar &
// 可以通过jps -l -v,jinfo pid来验证
- 找出cpu占比高的线程
// 找到cpu占比高的进程的pid
top -c
// 得到进程中的线程tid
ps -mp <pid> -o THREAD,tid,time | sort -k2r
// 将tid转16进制,并得到线程的堆栈信息
jstack -l <pid> | grep <thread-hex-id> -A 10
- 检测死锁
jstack -F pid来检测
- 要减少FullGC次数,最重要的一点是保证老年代的稳定,使用作用域等手段来保证对象及时被回收而不是被移动到老年代。 同时尽量避免使用超大对象(会因为担保机制进入老年代),采用合适的数据结构:long类型:8byte,而Long类型:24byte;
来源:CSDN
作者:qq_28128035
链接:https://blog.csdn.net/qq_28128035/article/details/104086566