原创

调优-jvm-jvm调优参数-gc垃圾回收-启动脚本-排查内存溢出-死锁-自我总结

202302收集到的易理解在线文档:
新生代理解
https://blog.csdn.net/weixin_42073629/article/details/110730560
新生代 老年代 永久代理解
https://blog.csdn.net/xiaokanfuchen86/article/details/117391855
Minor GC(少数)、Major GC(大多数)、Full GC(完全)理解 https://blog.csdn.net/zhangdx001/article/details/105490928
频繁gc(尤其是major gc和 full gc)可能导致系统卡顿无法使用,现象可能是有业务在刷(另外也可能有锁表sql,具体看插座锁表sql)
查看java gc情况
https://blog.csdn.net/u011490072/article/details/117038451


VM调优
什么时候调?
通过监控系统(如没有现成的系统,自己做一个简单的上报监控的系统也很容易)上对一些机器关键指标(gc time、gc count、各个分代的内存大小变化、机器的Load值与CPU使用率、JVM的线程数等)的监控报警,也可以看gc log和jstat等命令的输出,再结合线上JVM进程服务的一些关键接口的性能数据和请求体验,基本上就能定位出当前的JVM是否有问题,以及是否需要调优。

怎么调?
如果发现高峰期CPU使用率与Load值偏大,这个时候可以观察一些JVM的thread count以及gc count(可能主要是young gc count),如果这两个值都比以往偏大(也可以和一个历史经验值作对比),基本上可以定位是young gc频率过高导致,这个时候可以通过适当增大young区大小或者占比的方式来解决。
如果发现关键接口响应时间很慢,可以结合gc time以及gc log中的stop the world的时间,看一下整个应用的stop the world(STW)的时间是不是比较多。如果是,可能需要减少总的gc time,具体可以从减小gc的次数和减小单次gc的时间这两个维度来考虑,一般来说,这两个因素是一对互斥因素,我们需要根据实际的监控数据来调整相应的参数(比如新生代与老生代比值、eden与survivor比值、MTT值、触发cms回收的old区比率阈值等)来达到一个最优值。
如果发生full gc或者old cms gc非常频繁,通常这种情况会诱发STW的时间相应加长,从而也会导致接口响应时间变慢。这种情况,大概率是出现了“内存泄露”,Java里的内存泄露指的是一些应该释放的对象没有被释放掉(还有引用拉着它)。那么这些对象是如何产生的呢?为啥不会释放呢?对应的代码是不是出问题了?问题的关键是搞明白这个,找到相应的代码,然后对症下药。所以问题的关键是转化成寻找这些对象。怎么 找?综合使用jmap和MAT,基本就能定位到具体的代码。

开启gc日志和内存溢出日志相关的参数
-XX:+DisableExplicitGC 该参数将使JVM完全忽略系统的GC调用(不管使用的收集器是什么类型)。
-Xloggc:/opt/logs/jar-8083/gc.20191016134055.log 应该是gc的日志
-verbose:gc 开启日志打印打印gc情况,收集前-->收集后
-XX:+PrintGCDetails 打印gc详细日志
-XX:+PrintGCDateStamps 便于更精确地判断次GC操作之间的时间
-XX:+HeapDumpOnOutOfMemoryError JVM快死快死掉的时候,输出Heap Dump到指定文件
-XX:HeapDumpPath=/opt/apps/maintain-8083 结合上面的

另外还有gc相关的参数,比如 UseSerialGC UseParallelGC UseParalledlOldGC UseConcMarkSweepGC等

启动脚本:
java -Dserver.port=8083 -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8113 -server -jar -Dspring.profiles.active=sst -Xms1024m -Xmx1024m -XX:NewRatio=3 -XX:PermSize=256m -XX:MaxPermSize=256m -XX:InitialCodeCacheSize=64m -XX:ReservedCodeCacheSize=64m -Dlogging.path=/opt/logs/jar-8083 -Dserver.undertow.accesslog.dir=/opt/logs/jar-8083 -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=4 -XX:CMSInitiatingOccupancyFraction=72 -XX:+DisableExplicitGC -Xloggc:/opt/logs/jar/gc.20191016134055.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/apps/maintain-8083 -Dons.client.logRoot=/opt/logs/jar-8083 -Dons.client.logLevel=WARN -Dons.client.logFileMaxIndex=20 /opt/logs/jar-8083/xxxjar.jar
java -Dspring.profiles.active=prod -Djava.net.preferIPv4Stack=true -Xms1524m -Xmx2548m -XX:PermSize=256m -XX:MaxNewSize=912m -XX:MaxPermSize=912m -XX:ParallelGCThreads=4 -XX:+UseConcMarkSweepGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+DisableExplicitGC -jar ./inp-v1.0.1-20210616.jar

java -Xms128m -Xmx512m -XX:ConcGCThreads=2 -jar cmg*.jar &

参数解读:
-Xms1524m 初始堆大小
-Xmx2548m 最大堆大小
-XX:PermSize=256m 指非堆区初始化内存分配大小
-XX:MaxPermSize=256m 非堆区分配的内存的最大上限
-XX:MaxNewSize=912m 表示新生代可被分配的内存的最大上限;当然这个值应该小于-Xmx的值;
-XX:NewRatio=3 新生代 1/4 (反正分子都是1,分母+1)
-XX:InitialCodeCacheSize=64m 初始化的code cache的大小,默认500KB。这个值应该不小于系统最小内存页的大小。
-XX:ReservedCodeCacheSize=64m 设置为了JIT编译代码的最大代码cache大小。这个设置默认是240MB,如果关掉了tiered编译,则大小是48MB。这个设置必须比初始化的-XX:InitialCodeCacheSize=size设置值大。
-Dlogging.path=/opt/logs/jar-8083
-Dserver.undertow.accesslog.dir=/opt/logs/jar-8083 undertow是http服务器,所以就是指定下日志路径
-XX:+UseConcMarkSweepGC 设置并发收集器
-XX:ParallelGCThreads=4 设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:ConcGCThreads=2 并发GC的线程数量。默认值根据cpu的数量而定。
-XX:CMSInitiatingOccupancyFraction=72 该值代表老年代堆空间的使用率,默认值为68。当老年代使用率达到此值之后,并行收集器便开始进行垃圾收集,该参数需要配合UseCMSInitiatingOccupancyOnly一起使用,单独设置无效。
-XX:+UseCMSInitiatingOccupancyOnly 看上面
-XX:+DisableExplicitGC 该参数将使JVM完全忽略系统的GC调用(不管使用的收集器是什么类型)。
-Xloggc/opt/logs/jar-8083/gc.20191016134055.log 应该是gc的日志
-verbose:gc 开启日志打印打印gc情况,收集前-->收集后
-XX:+PrintGCDetails 打印gc详细日志
-XX:+PrintGCDateStamps 便于更精确地判断次GC操作之间的时间
-XX:+HeapDumpOnOutOfMemoryError JVM快死快死掉的时候,输出Heap Dump到指定文件
-XX:HeapDumpPath=/opt/logs/jar-8083 结合上面的
-Dons.client.logRoot=/opt/logs/jar-8083 RocketMQ的日志
-Dons.client.logLevel=WARN RocketMQ的日志
-Dons.client.logFileMaxIndex=20 RocketMQ的日志
-Djava.net.preferIPv4Stack=true RocketMQ的日志

java调优相关:来源:https://gitee.com/jiangjiesheng/java-optimization-and-debug-for-prod-env
jps -l 查看当前系统运行了哪些Java进程【给出进程号和包名】
jstat 查看jvm统计信息(好像没啥实际的作用)
jmap 手动导出内存镜像文件用于内存溢出分析
(结合jps -l 或ps -ef | grep 查到进程号
jmap -dump:format=b,file=java_pid9544_by_jmap.hprof 9544,
也可以通过参数自动导出 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./export/
分析工具:MemoryAnalyzer.exe(MAT工具)
jstack 实战死循环与死锁 ,由阻塞方法引起的死锁,看起来不动了【实用】
jstack -F 进程号 > jstack.deadlock.10892.txt # -F参数可以强制导出
或者 jstack -l  进程号 | grep deadlocks 进程号,直接展示结果。
报告中搜索 deadlock 关键词,如果没有死锁 No deadlocks found。
如果报告中有 Thread 1783: (state = BLOCKED),这个怎么定位?结合服务的log日志中线程号来定位?
不处理的危害可能导致java占用内存越来越大,这个时候就需要去脚本限制内存大小了
jvm参数再次说明:
-- 未验证
XmnXmsXmxXss有什么区别
Xmn、Xms、Xmx、Xss都是JVM对内存的配置参数,我们可以根据不同需要区修改这些参数,以达到运行程序的最好效果。

-Xms 堆内存的初始大小,默认为物理内存的1/64

-Xmx 堆内存的最大大小,默认为物理内存的1/4

-Xmn 堆内新生代的大小。通过这个值也可以得到老生代的大小:-Xmx减去-Xmn

-Xss 设置每个线程可使用的内存大小,即栈的大小。在相同物理内存下,减小这个值能生成更多的线程,当然操作系统对一个进程内的线程数还是有限制的,不能无限生成。线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。

除了这些配置,JVM还有非常多的配置,常用的如下:

堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-Xmn:新生代大小
-XX:NewRatio:设置新生代和老年代的比值。如:为3,表示年轻代与老年代比值为1:3
-XX:SurvivorRatio:新生代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:为3,表示Eden:Survivor=3:2,一个Survivor区占整个新生代的1/5  
-XX:MaxTenuringThreshold:设置转入老年代的存活次数。如果是0,则直接跳过新生代进入老年代
-XX:PermSize、-XX:MaxPermSize:分别设置永久代最小大小与最大大小(Java8以前)
-XX:MetaspaceSize、-XX:MaxMetaspaceSize:分别设置元空间最小大小与最大大小(Java8以后)
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行老年代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器新生代收集方式为并行收集时,使用的CPU数。并行收集线程数。
正文到此结束
本文目录