基于场景的JVM调优
前言
我们在面试大厂的Java
开发岗位的时候有一个问题经常会被问到,你在开发的过程中有没有遇到JVM
的参数的相关问题,本篇博客就带领大家去了解几个常见的JVM
的现象以及解决方法。
JVM的其它知识可参考我的博客—蹊源的Java笔记—JVM
正文
内存泄漏与内存溢出
异常堆栈信息“java.lang.OutOfMemoryError:java heap space
”
-
内存泄漏:指的是程序在动态分片一些临时对象,但是对象不会被
GC
正常回收。它始终占用内存。即对象可达但已无用(诸如缓存) - 内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。
所以内存泄漏只是内存溢出的一个可能诱发原因。
内存泄漏的常见场景
- 长生命周期的对象持有短生命周期对象的引用。(这是最常见的情况)
- 修改
hashset
对象的参数值,且参数是计算hash
值得字段(这个hash
对象已经不可获取,但又没正常被gc) - 机器的连接数和关闭时间设置(可以联想平安系统在连接多个数据库连接时,包内存溢出问题)
- 线程池使用了*队列,请求短时时间大量涌入,没有足够的线程去处理任务。
内存溢出的常见场景
-
堆内存溢出:当频繁生产新新对象时,内存占用量超出
jvm
分配的内存。 - 方法区溢出:程序加载的类过多,或者使用反射等动态代理生成类的技术,就可能导致该区发生内存溢出。
- 线程栈溢出:一般线程栈溢出,是由于递归太深或者方法调用层级过多导致的。
在生产环境如何去解决内存溢出的问题:
- 在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 进程号
获取该进程下,线程的占用的情况
这里的pid
是10进制的,我们需要获取其16进制的
进制转换:7390 ——>1cde
第三步:jstack
导出进程快照
现象二:内存溢出(OOM)
通常对dump
文件(进程的内存镜像)进行分析
获取dump文件的方式:
1.添加jar
启动参数 在发生OutOfMemoryError
时生成dump
文件 (通常来说dump
相对比较大)
2.使用jmap
获取,如获取 32652的dump
-
jmap
可以导出jvm
中存活对象的堆内存信息 -
jstack
可以导出此进程的堆栈信息 -
jmap
只能用比较小的项目,一般生产环境不能使用jmap
,需要比较长的时间
3.使用专门的工具对dump
文件进行分析
- 使用
JDK
自带的 jvisualvm
工具可以分析dumpwen List item
现象三:系统卡顿
首先我们要知道系统卡顿是由于GC
导致的。
GC对程序产生影响的情况:(影响从高到低)
-
FGC过于频繁:
FGC
通常是比较慢的,少则几百毫秒,多则几秒,正常情况FGC
每隔几个小时甚至几天才执行一次,对系统的影响还能接受。 -
YGC耗时过长:一般来说,
YGC
的总耗时在几十或者上百毫秒是比较正常的,虽然会引起系统卡顿几毫秒或者几十毫秒,这种情况几乎对用户无感知,对程序的影响可以忽略不计。 -
FGC耗时过长:
FGC
耗时增加,卡顿时间也会随之增加,尤其对于高并发服务,可能导致FGC
期间比较多的超时问题,可用性降低,这种也需要关注。 -
YGC过于频繁:即使
YGC
不会引起服务超时,但是YGC
过于频繁也会降低服务的整体性能,对于高并发服务也是需要关注的。
使用jstat查看虚拟机的GC情况
参数说明:
-
FGC:
full gc
的次数 -
FGCT:
full gc
的总时长
如果单次Full GC
过长,可以通过修改提高新生代和老生代的比例、选择合适的GC
方式等减少停顿时间。
full gc可能的场景:
- 老年代满了,由于内存分配担保策略,当晋升到老年代的对象大于了老年代的剩余空间时,就会触发
FGC
。 - 老年代的内存使用率达到了一定阈值(可通过参数调整),直接触发
FGC
。 -
Metaspace
(元空间)在空间不足时会进行扩容,当扩容到了-XX:MetaspaceSize
参数的指定值时,也会触发FGC
。 -
System.gc()
或者Runtime.gc()
被显式调用时,触发FGC
。 - 采用
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
工具进一步分析