基于场景的JVM调优

时间:2021-04-13 00:43:22


基于场景的JVM调优

前言

我们在面试大厂的​​Java​​​开发岗位的时候有一个问题经常会被问到,你在开发的过程中有没有遇到​​JVM​​​的参数的相关问题,本篇博客就带领大家去了解几个常见的​​JVM​​的现象以及解决方法。

JVM的其它知识可参考我的博客—​​蹊源的Java笔记—JVM​​

正文

内存泄漏与内存溢出

异常堆栈信息“​​java.lang.OutOfMemoryError:java heap space​​”

  • 内存泄漏:指的是程序在动态分片一些临时对象,但是对象不会被​​GC​​正常回收。它始终占用内存。即对象可达但已无用(诸如缓存)
  • 内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。

所以内存泄漏只是内存溢出的一个可能诱发原因。

内存泄漏的常见场景

  1. 长生命周期的对象持有短生命周期对象的引用。(这是最常见的情况)
  2. 修改​​hashset​​​对象的参数值,且参数是计算​​hash​​​值得字段(这个​​hash​​对象已经不可获取,但又没正常被gc)
  3. 机器的连接数和关闭时间设置(可以联想平安系统在连接多个数据库连接时,包内存溢出问题)
  4. 线程池使用了*队列,请求短时时间大量涌入,没有足够的线程去处理任务。

内存溢出的常见场景

  1. 堆内存溢出:当频繁生产新新对象时,内存占用量超出​​jvm​​分配的内存。
  2. 方法区溢出:程序加载的类过多,或者使用反射等动态代理生成类的技术,就可能导致该区发生内存溢出。
  3. 线程栈溢出:一般线程栈溢出,是由于递归太深或者方法调用层级过多导致的。

在生产环境如何去解决内存溢出的问题:

  • 在jar的启动参数添加: ​​-XX:+heapDumpOnOutofMemoryError​
  • 可以让虚拟机在出现内存溢出异常时​​Dump​​出当前的内存堆转储快照以便事后进行分析。

手段:通过内存映射分析工具(如​​Eclipse Memory Analyzer​​)

核心:通过判断内存中的对象是否是必要来判断是不是内存泄漏(即这对象是否是可达有用的)

现象一:CPU瞬间被占满

第一步: ​​top -c​​​ 查看当前占用​​CPU​​比较高的进程

top指令的参数

  • d:指定更新的间隔,以秒计算。
  • q:没有任何延迟的更新。如果使用者有超级用户,则​​top​​命令将会以最高的优先序执行。
  • c:显示进程完整的路径与名称。
  • S:累积模式,会将己完成或消失的子行程的​​CPU​​时间累积起来。
  • s:安全模式。
  • i:不显示任何闲置(​​Idle​​​)或无用(​​Zombie​​)的行程。
  • n:显示更新的次数,完成后将会退出​​top​​。

第二步: ​​top -Hp 进程号​​ 获取该进程下,线程的占用的情况

基于场景的JVM调优

这里的​​pid​​​是10进制的,我们需要获取其16进制的
进制转换:​​​7390 ——>1cde​

第三步:​​jstack​​ 导出进程快照

#在当前路径下导出进程快照
jstack -l 7390 > ./7390.stack

#获取占用线程比较高的线程的相关输出
cat 7390.stack |grep '1cde' -C 8

现象二:内存溢出(OOM)

通常对​​dump​​文件(进程的内存镜像)进行分析

获取dump文件的方式:
1.添加​​​jar​​​启动参数 在发生​​OutOfMemoryError​​​时生成​​dump​​​文件 (通常来说​​dump​​相对比较大)

-XX:+HeapDumpOnOutOfMemoryError(程序内存溢出自动转存dump)
-XX:HeapDumpPath=D:\sso\dump(转成dump文件路径)

2.使用​​jmap​​​获取,如获取 32652的​​dump​

  • ​jmap​​​可以导出​​jvm​​中存活对象的堆内存信息
  • ​jstack​​可以导出此进程的堆栈信息
  • ​jmap​​​只能用比较小的项目,一般生产环境不能使用​​jmap​​,需要比较长的时间
jmap -dump:format=b,file=./32652.hprof 32652

3.使用专门的工具对​​dump​​文件进行分析

  • 使用​​JDK​​​自带的 ​​jvisualvm​​​ 工具可以分析​​dumpwen List item​

基于场景的JVM调优

现象三:系统卡顿

首先我们要知道系统卡顿是由于​​GC​​导致的。

GC对程序产生影响的情况:(影响从高到低)

  • FGC过于频繁:​​FGC​​​通常是比较慢的,少则几百毫秒,多则几秒,正常情况​​FGC​​每隔几个小时甚至几天才执行一次,对系统的影响还能接受。
  • YGC耗时过长:一般来说,​​YGC​​的总耗时在几十或者上百毫秒是比较正常的,虽然会引起系统卡顿几毫秒或者几十毫秒,这种情况几乎对用户无感知,对程序的影响可以忽略不计。
  • FGC耗时过长:​​FGC​​​耗时增加,卡顿时间也会随之增加,尤其对于高并发服务,可能导致​​FGC​​期间比较多的超时问题,可用性降低,这种也需要关注。
  • YGC过于频繁:即使​​YGC​​​不会引起服务超时,但是​​YGC​​过于频繁也会降低服务的整体性能,对于高并发服务也是需要关注的。

使用jstat查看虚拟机的GC情况

jstat -gcutil 7378
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 2.66 77.68 91.77 91.59 82.62 949 6.188 288438 11884.693 11890.880

参数说明:

  • FGC:​​full gc​​的次数
  • FGCT:​​full gc​​的总时长

如果单次​​Full GC​​​过长,可以通过修改提高新生代和老生代的比例、选择合适的​​GC​​方式等减少停顿时间。

full gc可能的场景:

  1. 老年代满了,由于内存分配担保策略,当晋升到老年代的对象大于了老年代的剩余空间时,就会触发​​FGC​​。
  2. 老年代的内存使用率达到了一定阈值(可通过参数调整),直接触发​​FGC​​。
  3. ​Metaspace​​​(元空间)在空间不足时会进行扩容,当扩容到了​​-XX:MetaspaceSize​​​ 参数的指定值时,也会触发​​FGC​​。
  4. ​System.gc()​​​或者​​Runtime.gc()​​​被显式调用时,触发​​FGC​​。
  5. 采用​​CMS​​​收集器发生​​"Concurrent Mode Failure”​​​异常时,触发​​FGC​​。

我们处理​​fullgc​​​的方式就是分析当时的​​dump​​​文件,通过分析各区域的内存空间状况,从而定位​​fullgc​​的原因。

有两种方式获取fullgc前后dump文件:

  • 通过在jvm里添加参数配置:​​+HeapDumpBeforeFullGC,+HeapDumpAfterFullGC​
  • 通过jinfo命令设置VM参数:​​jinfo -flag +HeapDumpBeforeFullGC 进程号 ,jinfo -flag +HeapDumpAfterFullGC 进程号​
  • ​jmap​​并不能直接用在生产上,因当文件比较大的情况下时间比较长
  • 再获取​​dump​​​文件后可以通过​​JDK​​​中​​jvisualvm​​工具进一步分析

基于场景的JVM调优