jvm-常用参数和调优方案(超保姆级)

x33g5p2x  于2021-09-25 转载在 Java  
字(9.6k)|赞(0)|评价(0)|浏览(648)

Java-JVM-常用参数和调优方案

必须了解的内容

必须看过Java-JVM基础(调优必须知道)知道JVM大概的情况,这篇文章我博客里有

堆区(Java堆:所有的线程共享该区域)
通过new的方式创建的对象(一个类的实例)、数组所占的空间。

非堆区: (代码、常量、外部访问, 比如: 流在传输数据时所占用的资源等)

  1. 堆区还细分为:
    新生代( Eden空间、From Survivor空间、To Survivor空间)、老年代(Tenured Generation空间)。
  2. Java垃圾回收机制只作用于堆区,对非堆区没有作用。

调优注意事项:

  1. 最大堆内存与最大非堆内存的和绝对不能够超出操作系统的可用内存。
  2. 一个好的Web系统应该是每次http请求申请内存都能在Young gc回收掉,Full gc永不发生,当然这是最理想的情况
  3. xmn的值应该是保证够用(够http并发请求之用)的前提下设置得尽量小
  4. 堆大小建议不要超过8G内存 JVM调优不是内存越大越好,因为GC需要遍历堆内存,进行清除不需要的对象

以下都是我工作中遇到的并使用的一些参数,没写的就是还没有用到过

堆内存相关设置

-Xms

设置堆的最小空间大小。 建议和-Xmx(最大堆内存)设置一样

设置方式

-Xms4096M

-Xmx

设置堆的最大空间大小。
设置方式

-Xmx4096M

-Xmn

堆内新生代的大小。剩下的就是老年代的了: -Xmx减去-Xmn
设置方式

-Xmn1024M

-Xss

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

设置方式

-Xss256k

永久代(非堆)

-XX:PermSize(1.8之前)

设置永久代最小空间大小。 。建议和-XX:MaxPermSize设置一样

设置方式

-XX:PermSize=524m

-XX:MaxPermSize(1.8之前)

设置永久代最小空间大小。

设置方式

-XX:MaxPermSize=524m

-XX:MetaspaceSize(1.8之后)

1.8之前叫永久代,1.8之后叫元空间
初始原空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:
规则: 如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
建议设置此值,JVM默认20.8m 防止超过此值,扩充时候会不断触发Fuil GC ,我们给他默认调整为524m

设置方式

-XX:MetaspaceSize=524m

-XX:MaxMetaspaceSize(1.8之后)

设置永久代最大空间大小。默认是没有限制的。 本地物理内存多大那么就能多大
建议设置此值防止,无限制占用物理内存,和MetaspaceSize保持一致,这样就不会因为元空间满导致不断的Fuil GC ,而是直接报错java.lang.OutOfMemoryError: Metaspace,
这样我们就不会误解是堆内存不够的原因,而是元空间不够的原因,这样就能很好定位到问题

设置方式

-XX:MaxMetaspaceSize=524m

其他设置

-XX:NewRatio

设置年轻代和年老代的比值 ( 项目不同设置也会不同需要分析问题,反正尽量让老年代尽量少full gc)
例如: 3表示新生代占年老代的1/3,占整个堆内存的1/4,因为还有一个元空间

设置方式

-XX:NewRatio=3

-XX:SurvivorRatio

年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。
如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5

设置方式

-XX:SurvivorRatio=6

-XX:MaxTenuringThreshold

设置转入老年代的存活次数。如果是0,则直接跳过新生代进入老年代 JVM默认15
设置方式

-XX:MaxTenuringThreshold=5

-XX:+UseStringDeduplication

设置字符串去重

1.java应用内存里面的字符串占比大概是25%。
2.java应用内存里面的字符串有13.5%是重复的。
3.字符串的平均长度是45。

由于存在重复字符串导致高达13.5%的内存被浪费了!你可以用分析工具来分析看下你的应用中有多少内存是因为重复字符串被浪费掉的

注意只有:

  1. java8u20(1.8.0_200) 版本+ 才支持 -XX:+UseStringDeduplication 字符串去重
  2. 只适用于G1收集器
  3. 只清除长期存活的字符串重复对象 默认情况下,一个字符串对象经过3次GC以后还存活才会被列为去重的候选对象

设置方式

-XX:+UseStringDeduplication

-XX:PretenureSizeThreshold

手动指定对象大小,当对象达到指定大小时直接存放到老年代中,由于新生代大多使用复制算法,为了节省复制算法耗时

设置方式

XX:PretenureSizeThreshold=10M

-XX:+DisableExplicitGC

System.gc()默认会触发一次Full GC,如果在代码中不小心调用了System.gc()会导致JVM间歇性的暂停 ,开启后,会导致System.gc()调用变成一个空调用
如果发生这样的错误的话 :java.lang.OutOfMemoryError: Direct buffer memory.
那么就是我们项目中使用了Netty框架或者NIO 导致无法申请到足够的堆外内存,从而产生的问题
这时候我们就不能开启这个参数了,一般情况下是建议开启的

设置方式

-XX:+DisableExplicitGC

GC日志

需要分析GC日志的话需要添加下面这些参数

-XX:+PrintGCDateStamps:打印 GC 发生的时间戳。
-XX:+PrintTenuringDistribution:打印 GC 发生时的代龄信息。
-XX:+PrintGCApplicationStoppedTime:打印 GC 停顿时长
-XX:+PrintGCApplicationConcurrentTime:打印 GC 间隔的服务运行时长
-XX:+PrintGCDetails:打印 GC 详情,包括 GC 前/内存等。
-Xloggc:/home/log/gc.log:指定 GC log 的路径

一般设置下面这些就够了

-XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:./gclogs/gc.log

支持1.8和之前 jdk9之后不一样了自行百度

内容大概是这样的:

看不懂没关系有
在线分析工具
GCEasy: https://gceasy.io/gc-index.jsp

离线版的分析工具:
GCViewer
下载地址: 但是需要自己解压然后进入到java目录启动gcviewer.xx.jar

本人以提取好的gcviewer-1.37-SNAPSHOT.jar
链接:https://pan.baidu.com/s/1i_F9FQRJwaxKylHR-CtQ-Q
提取码:1234

gcviewer具体使用教程百度自行查找

GChisto:
gitHub下载: https://github.com/jewes/gchisto
需要使用IDEA 自行打包或者直接在项目里运行也行

本人以打包好的gc-gchisto.jar
链接:https://pan.baidu.com/s/1j3uI3T7XbMd5h2jxOwJGoQ
提取码:1234
需要使用java -jar gc-gchisto.jar 启动 然后把日子放进去就行
GChisto具体使用教程百度自行查找

堆日志

是堆栈溢出时候打印出日志文件
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/home/log/java_dump.hprof

手动导出dump文件命令:
ps -ef | grep java (左边第二个就是 PID)

jmap -dump:format=b,file=heapDump.hprof PID 进程号
注:后缀名必须hprof结尾,然后下载此文件到本地

使用 MAT Java 堆内存分析工具。
http://www.eclipse.org/mat/downloads.php 官网下载地址(新版需要JDK11+才能打开)
我这有旧版JDK1.8能打开的MAT
链接:https://pan.baidu.com/s/1x_oLOR7v6j9kW5wSELXOew
提取码:1234

工具下载好后解压文件夹,然后打开软件

之后new—>open heap dump选择下载的堆文件

具体使用方式查百度这里就不多啰嗦了

项目异常宕机日志

java程序遇到致命错误打印日志 比如:java线程莫名其妙死掉了
-XX:ErrorFile=/home/hd/java_error%p.log

使用 CrashAnalysis工具
https://github.com/xpbob/CrashAnalysis gitHua 下载地址

百度网盘CrashAnalysis-1.0
链接:https://pan.baidu.com/s/1oob9dAplWo73livzDj0MbA
提取码:1234

下载下来是jar 需要使用 java -jar CrashAnalysis-1.0-SNAPSHOT.jar 启动

然后使用工具把错误文件打开就行,就会自动分析问题

G1收集器

cms 和G1 的选择上 我觉得G1是永远的神

-XX:+UseG1GC (开启G1收集器)
不要手动设置新生代和老年代的大小,我们只要设置整个堆的大小, 如果手动设置了新生代和老年代的大小大小就意味着放弃了G1的自动调优。

-XX:MaxGCPauseMillis=400 (设置GC最大暂停时间200~500都行)

一般情况下这个值设置到200ms或者500ms都是可以的(不同情况下会不一样),但如果设置成50ms就不太合理。暂停时间设置的太短,就会导致出现G1跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。暂停时间只是一个目标,并不能总是得到满足。

设置JVM三种运行方式

我公司之前遇到的问题:
项目总是莫名其妙的宕机,经过不断的排查,通过,java宕机后自动生成的,hs_err_pid.log日志,发现是java解析字节码时候出错了.具体为啥解析错误,这个是JIT的问题了,我们也不知道
解决方案是:

  1. 更换高或者地版本的jdk
  2. 更换jvm的运行方式,从默认的混合模式换成解释模式或者编译模式

Java内存溢出问题总结

堆溢出

报错信息
java.lang.OutOfMemoryError: Java heap space

报错原因

1.无法在 Java 堆中分配对象
2.吞吐量爆增一瞬间把堆给充满了,而且这些对象还无法一次收回
3.应用程序无意中保存了对象引用,对象无法被 GC 回收
4.应用程序过度使用 finalizer。finalizer 对象不能被 GC 立刻回收。finalizer 由结束队列服务的守5.护线程调用,有时 finalizer 线程的处理能力无法跟上结束队列的增长

解决办法

1.将堆内存 dump 下来,使用 堆文件分析工具,分析一下,解决内存泄漏;
2.如果没有内存泄漏,使用 -Xmx 增大堆内存;
3.自定义的 finalizer对象,考虑其存在的必要性。

GC超载溢出

报错信息
java.lang.OutOfMemoryError:GC overhead limit exceeded

报错原因

垃圾回收器超过98%的时间用来做垃圾回收,但回收了不到2%的堆内存。

解决办法

1.添加 -XX:-UseGCOverheadLimit 这个参数去掉报警,但这只是一种掩耳盗铃的方式,一般出现 GC overhead limit exceeded 说明离真正的 OOM 也不远了;
2.将堆内存 dump 下来,使用堆文件分析工具,分析一下,解决内存泄漏;
3.如果没有内存泄漏,使用 -Xmx 增大堆内存;

永久代/元空间溢出

报错信息
java.lang.OutOfMemoryError: PermGen space 或者
java.lang.OutOfMemoryError: Metaspace(Java8及以上)

报错原因

永久代是 HotSot 虚拟机对 方法区的具体实现,存放了已被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。需要注意的是,在Java8后,永久代有了一个新名字:元空间,元空间使用的是本地内存。

永久代里存在的信息也有了若干变化:

  1. 字符串常量由永久代转移到堆中;
  2. 和永久代相关的JVM参数已移除。
  3. 出现永久代或元空间的溢出的原因可能有如下几种:
    1.有频繁的常量池操作(eg. String.intern),这种情况只适用于Java7之前应用;
    2.加载了大量的类信息,且没有及时卸载;
    3.应用部署完后没有重启。

解决办法

永久代/元空间 溢出的原因比较简单,解决方法有如下几种:
1.Java8前的应用:使用 -XX:MaxPermSize 增加永久代的大小;
2.Java8及以后的应用:如果设置了 -XX:MaxMetaSpaceSize,调整其大小或者移除掉该参数。
3.尝试重启JVM。

方法栈溢出

报错信息
java.lang.OutOfMemoryError : unable to create new native Thread

报错原因

虚拟机在拓展栈空间时,无法申请到足够的内存空间。一般出现在内存空间过小,但是又创建了大量的线程的场景。(就是没有内存创建线程了)

解决办法

  1. 通过-Xss降低的每个线程栈大小的容量
  2. 配置每个用户的最大线程数 可以使用ulimit -n 查看当前用户能使用的线程个数 ,使用pstree -p | wc -l 查看当前系统已使用的线程个数

配置每个用户的最大线程数方法如下:

修改/etc/security/limits.conf文件, 在文件末尾添加注意*不要少了
* soft nofile 204800
* hard nofile 204800
* soft nproc 204800
* hard nproc 204800

将以上保存好,然后重启服务器,再使用 ulimit -n

数组分配溢出

报错信息
java.lang.OutOfMemoryError: Requested array size exceeds VM limit

报错原因

这种情况一般是由于不合理的数组分配请求导致的

解决办法

1.消除代码逻辑错误或者调整堆大小。
2.修复应用程序中分配巨大数组的 bug

Swap分区溢出

报错信息
java.lang.OutOfMemoryError: Out of swap space

报错原因

这种情况一般是操作系统导致的,可能的原因有:

  1. swap 分区大小分配不足
  2. 机器上其他进程(程序)消耗了所有的物理内存。

解决办法

  1. kill -9 程序pid 杀死掉其他程序
  2. 加大swap(交换)内存
  3. 加大物理内存

本地方法溢出

报错信息
java.lang.OutOfMemoryError: stack_trace_with_native_method

报错原因

这种情况表明,本地方法在运行时出现了内存分配失败。
和java.lang.OutOfMemoryError : unable to create new native Thread 保存不同,
方法栈溢出出现在 JVM 的代码层面,而本地方法溢出发生在JNI代码或本地方法处。

解决办法

先检查下本地内存是否满了,在检查jvm堆的使用情况,如果都没啥问题那么,直接换JDK就行了 这是JVM底层的问题

SpringBoot-JVM配置方式

可以参照下面方式添加jvm参数调优

nohup java -Xmx2048m -Xms2048m  -jar target/demo-0.0.1-SNAPSHOT.jar>demo.log  2>&1 &

设置applicaton.yml
tomcat最大线程数,默认为200 : server.tomcat.max-threads: 800

Tomcat -JVM配置方式

进入tomcat/bin目录catalina.bat 中加入下面代码,位置cygwin=false前(文件搜索定位)
JAVA_OPTS="-Xms4096m -Xmx4096m "

Weblogc-JVM配置方式

修改setDomainEnv.cmd文件

我们只需要调整 64BIT的就行 32BIT不用管,这代表是32位机器,现在基本服务都是64位的了
if和else里的都需要调整,

启动服务必须使用 startWebLogic.sh 才有效 其他方式启动无效…具体怎么调整JVM自行百度搜索

查询是否配置成功

ps -ef | grep java
只找到自己进程的pid (左边第二个就是)然后
jinfo -flags pid
就能看到此程序生效的JVM参数,如果看不到就没有生效或者其他…自己找原因

检查问题的思路

别一遇到问题就找JVM的事情,他跟你有仇???
下面将展示linux-java问题排查正确思路,建议收藏起来以后遇到问题好排查

1.检查cpu

检查命令: top 查看程序进程的cpu是否太高

16核的cpu最高可以达到1600% 正常在1000%左右都是正常) 而1核cpu最高只能100%

查看当前linux 是多少核的 top 然后按下键盘1

cpu过高最终可能发生宕机,或者cpu强制杀死占用过高的进程

我们还可以使用top查看指定程序的内存使用情况
PID:进程的ID
  USER:进程所有者
  PR:进程的优先级别,越小越优先被执行
  NInice:值
  VIRT:进程占用的虚拟内存
  RES:进程占用的物理内存
  SHR:进程使用的共享内存
  S:进程的状态。S表示休眠,R表示正在运行,Z表示僵死状态,N表示该进程优先值为负数
  %CPU:进程占用CPU的使用率
  %MEM:进程使用的物理内存和总内存的百分比
  TIME+:该进程启动后占用的总的CPU时间,即占用CPU使用时间的累加值。
  COMMAND:进程启动命令名称

2.检查网络

如果网络不通畅或者波动较大的话会导致,用户访问失败,
命令: ping -s 65507 ip -c 1000

如果在监控过程中突然有一个time耗时特别长的,比如:
平均是3~4ms(毫秒)如果中途出现 30多或者300多那么就是网络不稳定

如果直接ping不通那么就是网络异常(导致系统访问失败)

微服项目需要将当前项目 , 相关联的服务器也ping下

3. 检查硬盘

命令: df -hT

主要看硬盘内存是否满了
主要涉及: 系统缓存,文件下载,内存交互… 最终可能发生宕机,死机…异常情况

4.检查内存使用情况

命令 free -m (单位是mb)

total 是内存总量
used 是当前以使用多少
free 是剩余空闲内存
Shared:多个进程共享的内存总额。
buff/cache 是系统缓存
available 还可以被 应用程序 使用的物理内存大小

available = free + buffer + cache (注:只是大概的计算方法)

对于应用程序来说,buffers/cached 是等于可用的,因为buffer/cached是为了提高文件读取的性能,当应用程序需在用到内存的时候,buffer/cached会很快地被回收。

5.排查JVM

提醒必须使用jdk1.8+的才行,1.7的话下面有些命令或者工作可能你使用不了

1.排查性能方面或者死锁

使用Arthas工具或者JProfiler 工具 ,这两种工具使用教程我在博客里有自行搜索,
我建议使用Arthas因为可用不用重启项目对项目影响最小

2.jvm内部各项数据情况

使用jstat命令,比可视化软件好用多了,前提是用数量了
命令如下:
ps -ef | grep java 找到PID(左边第二个)
jstat -gcutil PID 5000

效果图(单位秒):

注意: 别误会了,图中的时间都是叠加的 比如YGC是17.929秒 ,是295次Young GC 所有总和,可以看到295~296之间差55毫秒而这55毫秒,就是1次Young GC的时间 ,其他以此类推

参数介绍:

显示列名具体描述
S0C年轻代中第一个survivor(幸存区)的容量 (字节)
S1C年轻代中第二个survivor(幸存区)的容量 (字节)
S0U年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
S1U年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
EC年轻代中Eden(伊甸园)的容量 (字节)
EU年轻代中Eden(伊甸园)目前已使用空间 (字节)
OCOld代的容量 (字节)
OUOld代目前已使用空间 (字节)
PCPerm(持久代)的容量 (字节)
PUPerm(持久代)目前已使用空间 (字节)
YGC从应用程序启动到采样时年轻代中gc次数
YGCT从应用程序启动到采样时年轻代中gc所用时间(秒)
FGC从应用程序启动到采样时old代(全gc)gc次数
FGCT从应用程序启动到采样时old代(全gc)gc所用时间(秒)
GCT从应用程序启动到采样时gc用的总时间(秒)

JVM在深度点的排查就需要 jstat的其他命令了,以上基本能解决日常百分之90以上的jvm问题了,在深入的话,其实就算发现问题,可能你也可能没啥办法去解决,因为都是java内部本身的问题,基本都是直接换JDK版本
比如: 类的加载情况, 类的编译情况,已经各堆之间更细节的分布情况…这个我博客里有jstat的细节方面的命令自行查找

3.排查项目其他参数配置

如果按照上面都没找到问题所在,那么这就棘手了,我之前也遇到过找了1个多星期的问题,最后别人告诉我,拟生产环境统一配置中没有分别给各自,配置各自应用的IP地址,导致每次访问时候都要把他所有前面的服务器都走一遍,所以访问一直卡10秒,很固定的那种,我叫哪一个气啊…

所以很多时候都是人为的问题,你可以找到一个对项目全部配置都很了解的人,让他全部检查一遍确认没问题才行

6排查代码

以上都没问题了那么,就只能从代码入手了,如果是性能方面的话,

  1. 优化sql语句
  2. 优化数据存储结构
  3. 优化逻辑处理,比如采用多线程同时处理
  4. 检查代码处理数据的内容是否非常大,如果非常大的话能不能分批处理…
  5. 检查接口全调用链,中有没有 sleep, wait ,线程锁…
  6. 检查有没有循环嵌套特别多的
  7. 检查字符串有没有拼接多的,并且没有使用StringBuffer的地方
  8. 检查对象是否可以使用clone而不是从新new对象
  9. 检查各种集合使用是否是最忧集合
  10. 检查当前方法有没有使用Aop,拦截器,过滤器,监听器,给拦截了,…
  11. 调查此接口是否必须实时处理,如果不需要实时处理那么我们可以异步
  12. …其他

java进程突然挂了解决方案

java进程突然挂了无外乎三种情况:

  1. linux的OOM killer杀死
  2. JVM自身故障
  3. jvm的OOM导致进程退出

先检查是不是linux的OOM killer杀死的java

cd /var/log
grep -C 5 "java" messages

这就有问题了,

dumping core 这是程序崩溃了 - 我们可以看jvm生成的hs_err_pid.log 日志(系统自动生成)
或者
如果出现 kernel: Out of memory: Kill process意味着整个系统的内存已经不足,如果不杀死进程的话,就会导致系统的崩溃.

具体问题这个可以看 jvm 配置的 各种错误日志
在上面都有配置教程,3大日志(堆日志,致命日志,gc日志)
点赞 -收藏-关注-便于以后复习和收到最新内容有其他问题在评论区讨论-或者私信我-收到会在第一时间回复如有侵权,请私信联系我感谢,配合,希望我的努力对你有帮助^_^

相关文章