如果你的项目在生产环境内存资源占用异常,频繁FullGC,影响用户使用的流畅性及系统稳定性,绝大部分的问题都出自于业务代码本身的问题,在JVM调优里面也不例外,要减少GC的频率,进行JVM调优。
jvm参数
# 查看当前程序启动时的jvm参数
java -XX:+PrintCommandLineFlags -version
# -XX:InitialHeapSize=523137920 -XX:MaxHeapSize=8370206720 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
# openjdk version "1.8.0_322"
# OpenJDK Runtime Environment (build 1.8.0_322-b06)
# OpenJDK 64-Bit Server VM (build 25.322-b06, mixed mode)
java -XX:+Print
# 查看jvm初始参数值
java -XX:+PrintFlagsInitial -version
# 查看jvm最终参数值
java -XX:+PrintFlagsFinal -version
# 查看这些参数一共有多少种
java -XX:+PrintFlagsInitial -version | wc -l
java -XX:+PrintFlagsFinal -version | wc -l
java -Xms500M -Xmx500M \ # 最小堆、最大堆 (2个设置相等即可)
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \ # 使用G1垃圾回收器
-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause \ # 设置日志参数(5个20M文件,循环)
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof \ # 线上环境蹦的时候自动导出堆dump文件 `dump.hprof`
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=50001 \
-jar \
-Ddefault.client.encoding="UTF-8" -Dfile.encoding="UTF-8" -Duser.language="Zh" -Duser.region="CN" \
app.jar \
--spring.profiles.active=prod
模拟实战
# 最小堆:30M 最大堆:30M 打印gc日志信息
java -Xms30M -Xmx30M -XX:+PrintGC com.mashibing.jvm.gc.T15_FullGC_Problem01
# 查看当前所有java进程号pid
jps
# 实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器
top
# 显示指定进程信息
jinfo pid
# 显示堆内存信息
jstat -gc pid
# 动态显示 - 每1秒钟刷新一次
jstat -gc pid 1000
# 显示指定进程的所有线程 (一般用来查看是否存在死锁)
# ex: waiting on <0x0000000088ca3310> (a java.lang.Object)
# 假如有一个进程中100个线程,很多线程都在waiting on <xx> ,一定要找到是哪个线程持有这把锁
# 怎么找?搜索jstack dump的信息,找<xx> ,看哪个线程持有这把锁RUNNABLE
jstack pid # 一次性全部显示
jstack pid | more # 通过回车查看更多信息
# 查看指定进程前10行对象数据(内存分配的详细情况。例如实例个数,大小等)
jmap -histo pid | head -10
实战调优
你在项目中都使用了哪些参数打印GC?
具体的参数名称记不清楚了,但是我一般在项目中输出详细的 GC 日志,并加上可读性强的 GC 日志的时间戳。特别情况下我还会追加一些反映对象晋升情况和堆详细信息的日志,这些会单独打到gc.log文件中用来排查问题。另外,OOM 时自动 Dump 堆栈,我一般也会进行配置。
常用的调优工具有哪些?
JDK内置的命令行:jps
(查看jvm进程信息)、jstat
(监视jvm运行状态的,比如gc情况、jvm内存情况、类加载情况等)、jinfo
(查看jvm参数的,也可动态调整)、jmap
(生成dump文件的,在dump的时候会影响线上服务)、jhat
(分析dump的,但是一般都将dump导出放到mat上分析)、jstack
(查看线程的)。
JDK内置的可视化界面:JConsole、VisualVM
,这两个在QA环境压测的时候很有用。
阿里巴巴开源的arthas
:神器,线上调优很方便,安装和显示效果都很友好。
如果有一个系统,内存一直消耗不超过10%,但是观察GC日志,发现FGC总是频繁产生,会是什么引起的?
检查下系统是否存在System.gc() ;
线上一个系统跑一段时间就栈溢出了,怎么办 ?
首先检查下是否有死归这种无限递归的程序或者递归方法太多
可以看下栈大小,若太小则可以指定-Xss参数设置栈大小
系统CPU经常100%,如何调优?
CPU100%,那肯定是有线程一直在占用着系统资源,所以具体方法如下:
- 找出哪个进程cpu占用高(
top
命令) - 该进程中的哪个线程cpu占用高(
top -Hp $pid
命令) - 将十进制的tid转化为十六进制(
printf %x $tid
命令) - 导出该线程的堆栈 (
jstack $pid >$pid.log
命令) - 查找哪个方法(栈帧)消耗时间 (
less $pid.log
) - 可以确认工作线程占比高还是垃圾回收线程占比高
- 修改代码
系统内存飙高,如何查找问题?
- 找出哪个进程内存占用高(
top
命令) - 查看jvm进程号(
jps
命令) - 导出堆内存 (
jmap
命令生成dump文件,注意:线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿,所以操作前最好先从负载均衡里摘掉。) - 分析dump文件 (比如
mat
软件)
大型项目如何进行性能瓶颈调优
-
数据库与SQL优化:一般dba负责数据库优化,比如集群主从等。研发负责SQL优化,比如索引、分库分表等。
-
集群优化:一般OP负责,让整个集群可以很容易的水平扩容,再比如tomcat/nginx的一些配置优化等。
-
硬件升级:选择最合适的硬件,充分利用资源。
-
代码优化:很多细节,可以参照阿里巴巴规范手册和安装sonar插件这种检测代码质量的工具。也可以适当的运用并行,比如CountDownLatch等工具。
-
jvm优化:内存区域大小设置、对象年龄达到次数晋升老年代参数的调整、选择合适的垃圾收集器以及合适的垃圾收集器参数、打印详细的GC日志和oom的时候自动生成dump。
-
操作系统优化
你实际遇到调优的场景
之前都是采取zipkin
做分布式链路追踪,后来换成了skywalking
,所以将 zipkin
相关配置和代码都移除了,但是忘记移除maven坐标
了,运行一段时间后导致了频繁full gc ,最后oom了。
因为配置了oom后自动生成dump文件,所以分析dump文件发现大对象是zipkin包里的ConcurrentHashMap$Node
,通过观察zipkin的自动配置类ZipkinAutoConfiguration
发现即使没有任何zipkin的配置,只要有zipkin的依赖都会创建一个异步报告者,默认的采样率是10%,所以即使不配置相关配置项,也会以默认采样率10%,发送到zipkin,这是默认的地址是http://localhost:9411/
,此时发送到localhost:9411
显然会连接拒绝。导致度量中的异常实例堆积,从而OOM。
private float probability = 0.1f;
@ConfigurationProperties("spring.zipkin")
public class ZipkinProperties {
private String baseUrl = "http://localhost:9411/";
}
附录
GC常用参数
-Xmn
:年轻代-Xms
:最小堆-Xmx
:最大堆-Xss
:栈空间-XX:+UseTLAB
:使用TLAB,默认打开-XX:+PrintTLAB
:打印TLAB的使用情况-XX:TLABSize
:设置TLAB大小-XX:+DisableExplictGC
:禁用System.gc()不管用 ,防止FGC-XX:+PrintGC
:打印GC日志-XX:+PrintGCDetails
:打印GC详细日志信息-XX:+PrintHeapAtGC
:打印GC前后的详细堆栈信息-XX:+PrintGCTimeStamps
:打印时间戳-XX:+PrintGCApplicationConcurrentTime
:打印应用程序时间-XX:+PrintGCApplicationStoppedTime
:打印暂停时长-XX:+PrintReferenceGC
:记录回收了多少种不同引用类型的引用-verbose:class
:类加载详细过程-XX:+PrintVMOptions
:jvm参数-XX:+PrintFlagsFinal
:-XX:+PrintFlagsInitial 必须会用-Xloggc:opt/log/gc.log
:gc日志的路径以及文件名称-XX:MaxTenuringThreshold
:升代年龄,最大值15
Parallel常用参数
-XX:SurvivorRatio
:年轻代中eden和from/to的比值。比如设置3就是eden:survivor=3:2,也就是from和to各占1,eden占用3-XX:PreTenureSizeThreshold
:大对象到底多大-XX:MaxTenuringThreshold
:升代年龄,最大值15-XX:+ParallelGCThreads
:并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同-XX:+UseAdaptiveSizePolicy
:自动选择各区大小比例
CMS常用参数
-XX:+UseConcMarkSweepGC
:设置年老代为并发收集-XX:ParallelCMSThreads
:CMS线程数量-XX:CMSInitiatingOccupancyFraction
:使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)-XX:+UseCMSCompactAtFullCollection
:在FGC时进行压缩-XX:CMSFullGCsBeforeCompaction
:多少次FGC之后进行压缩-XX:+CMSClassUnloadingEnabled
:年老代启用CMS,但默认是不会回收永久代(Perm)的。此处对Perm区启用类回收,防止Perm区内存满。-XX:CMSInitiatingPermOccupancyFraction
:达到什么比例时进行Perm回收GCTimeRatio
:设置GC时间占用程序运行时间的百分比-XX:MaxGCPauseMillis
:停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代
G1常用参数
-XX:+UseG1GC
:开启G1-XX:MaxGCPauseMillis
:建议值,G1会尝试调整Young区的块数来达到这个值-XX:GCPauseIntervalMillis
:GC的间隔时间-XX:+G1HeapRegionSize
:分区大小,建议逐渐增大该值,1 2 4 8 16 32。随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长 ZGC做了改进(动态区块大小)G1NewSizePercent
:新生代最小比例,默认为5%G1MaxNewSizePercent
:新生代最大比例,默认为60%GCTimeRatio
:GC时间建议比例,G1会根据这个值调整堆空间ConcGCThreads
:线程数量InitiatingHeapOccupancyPercent
:启动G1的堆空间占用比例
评论区