前言
JVM作为Java进阶的知识,是需要Java程序员不断深度和理解的。
本篇博客介绍JVM调优的相关知识,给出了一个demo案例,介绍了JVM调优的主要参数;介绍了jdk自带的jvm分析工具的使用;给出了一个内存溢出的调优场景,逐步分析定位问题,以及发生死锁的分析案例。
其他相关的JVM博客文章如下:
- Java进阶(1)——JVM的内存分配 & 反射Class类的类对象 & 创建对象的几种方式 & 类加载(何时进入内存JVM)& 注解 & 反射+注解的案例
- Java进阶(4)——结合类加载JVM的过程理解创建对象的几种方式:new,反射Class,克隆clone(拷贝),序列化反序列化
- Java进阶(垃圾回收GC)——理论篇:JVM内存模型 & 垃圾回收定位清除算法 & JVM中的垃圾回收器
系列文章合集如下:
【合集】Java进阶——Java深入学习的笔记汇总 & 再论面向对象、数据结构和算法、JVM底层、多线程、类加载 …
目录
- 前言
- 引出
- JVM优化入门
- 案例demo代码
- JVM调优参数
- 使用jdk自带的工具分析
- jconsole.exe
- jvisualvm.exe
- 内存溢出的调优场景
- top命令发现是java
- jps 查看java进程
- jinfo 63205 查看JVM参数配置
- jstat 查看java进程内存分配
- jstack 打印出进程内部栈的调用链信息
- top -p 29046 -H 打印出进程内部线程的CPU占用情况
- jmap -histo 查看进程中的类,以及类的实例个数
- 发生死锁的调优场景
- 线程的状态
- 使用jstack进行分析
- 总结
引出
1.JVM调优的相关知识,给出了一个demo案例;
2.JVM调优的主要参数;
3.jdk自带的jvm分析工具的使用;
3.内存溢出的调优场景,逐步分析定位问题;
4.发生死锁的分析案例
JVM优化入门
案例demo代码
https://gitee.com/pet365/java-gc-demo
内存溢出
调用链
JDK1.8默认采用GC回收器为:PS + PO组合
如果我们不切换到G1回收器的情况下,如何进行参数调优!
什么是调优?
- 根据需求进行JVM规划&预调优
- JVM环境卡顿(找到系统瓶颈:压测)
- 解决JVM运行期间的问题(内存泄漏,内存溢出……)
调优,都必须根据业务场景来调优,不能假设,假设式的调优都是耍流氓!
JVM调优参数
调优参数:java -X & java -XX
开头的这些非标准参数!
主要是java -XX开头的参数,但是没有文档信息介绍
[root@iZuf61wy7p4tbr7lmwv18iZ ~]# java -X
-Xmixed 混合模式执行(默认)
-Xint 仅解释模式执行
-Xbootclasspath:<用 : 分隔的目录和 zip/jar 文件>
设置引导类和资源的搜索路径
-Xbootclasspath/a:<用 : 分隔的目录和 zip/jar 文件>
附加在引导类路径末尾
-Xbootclasspath/p:<用 : 分隔的目录和 zip/jar 文件>
置于引导类路径之前
-Xdiag 显示附加诊断消息
-Xnoclassgc 禁用类垃圾收集
-Xincgc 启用增量垃圾收集
-Xloggc:<file> 将 GC 状态记录在文件中(带时间戳)
-Xbatch 禁用后台编译
-Xms<size> 设置初始 Java 堆大小
-Xmx<size> 设置最大 Java 堆大小
-Xss<size> 设置 Java 线程堆栈大小
-Xprof 输出 cpu 分析数据
-Xfuture 启用最严格的检查,预计会成为将来的默认值
-Xrs 减少 Java/VM 对操作系统信号的使用(请参阅文档)
-Xcheck:jni 对 JNI 函数执行其他检查
-Xshare:off 不尝试使用共享类数据
-Xshare:auto 在可能的情况下使用共享类数据(默认)
-Xshare:on 要求使用共享类数据,否则将失败。
-XshowSettings 显示所有设置并继续
-XshowSettings:system
(仅限 Linux)显示系统或容器
配置并继续
-XshowSettings:all
显示所有设置并继续
-XshowSettings:vm 显示所有与 vm 相关的设置并继续
-XshowSettings:properties
显示所有属性设置并继续
-XshowSettings:locale
显示所有与区域设置相关的设置并继续
-X 选项是非标准选项。如有更改,恕不另行通知。
-XX参数主要有3种:行为参数,调优参数,调试参数
行为参数(功能开关)
-XX:-DisableExplicitGC 禁止调用System.gc();但jvm的gc仍然有效
-XX:+MaxFDLimit 最大化文件描述符的数量限制
-XX:+ScavengeBeforeFullGC 新生代GC优先于Full GC执行
-XX:+UseGCOverheadLimit 在抛出OOM之前限制jvm耗费在GC上的时间比例
-XX:-UseConcMarkSweepGC 对老年代采用并发标记交换算法进行GC
-XX:-UseParallelGC 启用并行GC
-XX:-UseParallelOldGC 对Full GC启用并行,当-XX:-UseParallelGC启用时该项自动启用
-XX:-UseSerialGC 启用串行GC
-XX:+UseThreadPriorities 启用本地线程优先级
性能调优
-XX:LargePageSizeInBytes=4m 设置用于Java堆的大页面尺寸
-XX:MaxHeapFreeRatio=70 GC后java堆中空闲量占的最大比例
-XX:MaxNewSize=size 新生成对象能占用内存的最大值
-XX:MaxPermSize=64m 老年代对象能占用内存的最大值
-XX:MinHeapFreeRatio=40 GC后java堆中空闲量占的最小比例
-XX:NewRatio=2 新生代内存容量与老生代内存容量的比例
-XX:NewSize=size 新生代对象生成时占用内存的默认值
-XX:ReservedCodeCacheSize=32m 保留代码占用的内存容量
-XX:ThreadStackSize=512 设置线程栈大小,若为0则使用系统默认值
-XX:+UseLargePages 使用大页面内存
调试参数
-XX:-CITime 打印消耗在JIT编译的时间
-XX:ErrorFile=./hs_err_pid<pid>.log 保存错误日志或者数据到文件中
-XX:-ExtendedDTraceProbes 开启solaris特有的dtrace探针
-XX:HeapDumpPath=./java_pid<pid>.hprof 指定导出堆信息时的路径或文件名
-XX:-HeapDumpOnOutOfMemoryError 当首次遭遇OOM时导出此时堆中相关信息
-XX:OnError="<cmd args>;<cmd args>" 出现致命ERROR之后运行自定义命令
-XX:OnOutOfMemoryError="<cmd args>;<cmd args>" 当首次遭遇OOM时执行自定义命令
-XX:-PrintClassHistogram 遇到Ctrl-Break后打印类实例的柱状信息,与jmap -histo功能相同
-XX:-PrintConcurrentLocks 遇到Ctrl-Break后打印并发锁的相关信息,与jstack -l功能相同
-XX:-PrintCommandLineFlags 打印在命令行中出现过的标记
-XX:-PrintCompilation 当一个方法被编译时打印相关信息
-XX:-PrintGC 每次GC时打印相关信息
-XX:-PrintGCDetails 每次GC时打印详细信息
-XX:-PrintGCTimeStamps 打印每次GC的时间戳
-XX:-TraceClassLoading 跟踪类的加载信息
-XX:-TraceClassLoadingPreorder 跟踪被引用到的所有类的加载信息
-XX:-TraceClassResolution 跟踪常量池
-XX:-TraceClassUnloading 跟踪类的卸载信息
-XX:-TraceLoaderConstraints 跟踪类加载器约束的相关信息
可以参考:https://blog.csdn.net/geejkse_seff/article/details/124288313
java -jar -XX:+UseG1GC -Xms200m -Xmx200m -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=8 -XX:G1HeapRegionSize=16m -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=5 -XX:G1MaxNewSizePercent=40 -XX:TargetSurvivorRatio=50 -XX:MaxTenuringThreshold=15 -XX:InitiatingHeapOccupancyPercent=45 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:/gc.log gc-1.0.jar
使用jdk自带的工具分析
通过DOS命令窗口,启动jar包
java -jar -Xms200m -Xmx200M -XX:+PrintGC -XX:-UseParallelGC gc-1.0.jar
jconsole.exe
选择不安全连接
查看VM概要,有启动时设置的参数
查看控制台概览,堆内存不断增加
年轻代的gc回收
可以手动GC
jvisualvm.exe
另外一个工具 jvisualvm.exe
能够监测到死锁的产生
生成dump文件查看
控制台进行各种GC
垃圾回收
开始出现Full GC
此时,jvm只在回收垃圾
内存溢出的调优场景
在Linux系统上运行:
java -jar -Xms200m -Xmx200M -XX:+PrintGC -XX:-UseParallelGC ./gc-1.0.jar
top、 jps 、jinfo、jstat 、jmap
另开一个窗口,去使用上述的命令:
内存溢出在实际的生产环境中经常会遇到,比如,不断的将数据写入到一个集合中,出现了死循环,读取超大的文件等等,都可能会造成内存溢出。如果出现了内存溢出,首先我们需要定位到发生内存溢出的环节,并且进行分析,是正常还是非正常情况,如果是正常的需求,就应该考虑加大内存的设置,如果是非正常需求,那么就要对代码进行修改,修复这个bug。首先,我们得先学会如何定位问题,然后再进行分析。
cpu飙升
top命令发现是java
jps 查看java进程
jps
jinfo 63205 查看JVM参数配置
jinfo 63205 查看参数配置
[root@192 ~]# jinfo 63205
Attaching to process ID 63205, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.371-b11
Java System Properties:
java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.371-b11
sun.boot.library.path = /root/software/jdk/jdk1.8.0_371/jre/lib/amd64
java.protocol.handler.pkgs = org.springframework.boot.loader
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 = CN
user.dir = /usr/local/software/jar/java-gc-demo
java.vm.specification.name = Java Virtual Machine Specification
PID = 63205
java.runtime.version = 1.8.0_371-b11
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = /root/software/jdk/jdk1.8.0_371/jre/lib/endorsed
line.separator =
java.io.tmpdir = /tmp
java.vm.specification.vendor = Oracle Corporation
os.name = Linux
sun.jnu.encoding = UTF-8
java.library.path = /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
spring.beaninfo.ignore = true
java.specification.name = Java Platform API Specification
java.class.version = 52.0
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
os.version = 3.10.0-1160.95.1.el7.x86_64
user.home = /root
user.timezone = Asia/Shanghai
catalina.useNaming = false
java.awt.printerjob = sun.print.PSPrinterJob
file.encoding = UTF-8
java.specification.version = 1.8
catalina.home = /tmp/tomcat.10050.2643101067280109420
user.name = root
java.class.path = target/spring-gc-demo-1.0-SNAPSHOT.jar
java.vm.specification.version = 1.8
sun.arch.data.model = 64
sun.java.command = target/spring-gc-demo-1.0-SNAPSHOT.jar
java.home = /root/software/jdk/jdk1.8.0_371/jre
user.language = zh
java.specification.vendor = Oracle Corporation
awt.toolkit = sun.awt.X11.XToolkit
java.vm.info = mixed mode
java.version = 1.8.0_371
java.ext.dirs = /root/software/jdk/jdk1.8.0_371/jre/lib/ext:/usr/java/packages/lib/ext
sun.boot.class.path = /root/software/jdk/jdk1.8.0_371/jre/lib/resources.jar:/root/software/jdk/jdk1.8.0_371/jre/lib/rt.jar:/root/software/jdk/jdk1.8.0_371/jre/lib/jsse.jar:/root/software/jdk/jdk1.8.0_371/jre/lib/jce.jar:/root/software/jdk/jdk1.8.0_371/jre/lib/charsets.jar:/root/software/jdk/jdk1.8.0_371/jre/lib/jfr.jar:/root/software/jdk/jdk1.8.0_371/jre/classes
java.awt.headless = true
java.vendor = Oracle Corporation
catalina.base = /tmp/tomcat.10050.2643101067280109420
java.specification.maintenance.version = 4
file.separator = /
java.vendor.url.bug = http://bugreport.sun.com/bugreport/
sun.io.unicode.encoding = UnicodeLittle
sun.cpu.endian = little
sun.cpu.isalist =
VM Flags:
Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=209715200 -XX:MaxHeapSize=209715200 -XX:MaxNewSize=69730304 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=69730304 -XX:OldSize=139984896 -XX:+PrintGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -Xms200m -Xmx200M -XX:+PrintGC
发现参数 -Xms200m -Xmx200M -XX:+PrintGC似乎并没有不合理的设置
jstat 查看java进程内存分配
jstat -gc 63205
- S0C: 年轻代中第一个survivor(幸存区)的容量(字节)
- S1C: 年轻代中第二个survivor(幸存区)的容量(字节)
- S0U: 年轻代中第一个survivor(幸存区)目前已使用空间(字节)
- S1U: 年轻代中第二个survivor(幸存区)目前已使用空间(字节)
- EC: 年轻代中Eden(伊甸园)的容量(字节)
- EU: 年轻代中Eden(伊甸园)目前已使用空间(字节)
- OC: Old代的容量(字节)
- OU: Od代目前已使用空间(字节)
- MC: metaspace(元空间)的容量(字节)
- MU: metaspace(元空间)目前己使用空间(字节)
- YGC:从应用程序启动到采样时年轻代中gC次数
- YGCT: 从应用程序启动到采样时年轻代中gC所用时间(S)
- FGC:从应用程序启动到采样时old代(全gc) gc次数
- FGCT:从应用程序启动到采样时old代(全gc) gc所用时间(s)
- GCT:从应用程序启动到采样时gc用的总时间(s)
jstack 打印出进程内部栈的调用链信息
jstack 63205
top -p 29046 -H 打印出进程内部线程的CPU占用情况
top -p 63205 -H
jmap -histo 查看进程中的类,以及类的实例个数
jmap -histo 4498 | head -20 #查看前20条信息
jmap -histo 63205 | head -20
剩余的工作:去调整你的业务代码,但是jmap的缺陷在于:它在导出内存图的时候,会STW,所以需要少用!
最好在测试环境中去 复盘出 生产环境的问题;然后在测试机上去使用jmap命令!
jmap -dump:format=b,file=/root/dump4498.hprof 4498 #将进程中的堆的所有信息快照出来
导出快照仍旧会导致STW问题,会导致所有业务线程卡死!
所以什么时间生成堆文件呢!
java -jar -Xms200m -Xmx200M -XX:+PrintGC -XX:-UseParallelGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/root/springbootgc.hprof ./gc-1.0.jar
-XX:+HeapDumpOnOutOfMemoryError JVM由于内存不足宕机的时候,自动生成
-XX:HeapDumpPath=/root/springbootgc.hprof 堆文件的存储位置
发生死锁的调优场景
有些时候我们需要查看下jvm中的线程执行情况,比如,发现服务器的CPU的负载突然增高了、出现了死锁、死循环等,我们该如何分析呢?由于程序是正常运行的,没有任何的输出,从日志方面也看不出什么问题,所以就需要看下jvm的内部线程的执行情况,然后再进行分析查找出原因。这个时候,就需要借助于jstack命令了,jstack的作用是将正在运行的jvm的线程情况进行快照,并且打印出来:
线程的状态
在Java中线程的状态一共被分成6种:
-
初始态(NEW):创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
-
运行态(RUNNABLE):在Java中,运行态包括 就绪态 和 运行态。
- 就绪态:该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行,所有就绪态的线程存放在就绪队列中
- 运行态:获得CPU执行权,正在执行的线程,由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程
-
阻塞态(BLOCKED):
- 当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。
- 而在Java中,阻塞态专指请求锁失败时进入的状态。
- 由一个阻塞队列存放所有阻塞态的线程。
- 处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。
-
等待态(WAITING)
- 当前线程中调用wait、join、park函数时,当前线程就会进入等待态。
- 也有一个等待队列存放所有等待态的线程。
- 线程处于等待态表示它需要等待其他线程的指示才能继续运行。
- 进入等待态的线程会释放CPU执行权,并释放资源(如:锁)
-
超时等待态(TIMED_WAITING)
- 当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时,就会进入该状态;
- 它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒;
- 进入该状态后释放CPU执行权 和 占有的资源。
- 与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
-
终止态(TERMINATED)
线程执行结束后的状态
使用jstack进行分析
jstack 63205
总结
1.JVM调优的相关知识,给出了一个demo案例;
2.JVM调优的主要参数;
3.jdk自带的jvm分析工具的使用;
3.内存溢出的调优场景,逐步分析定位问题;
4.发生死锁的分析案例