JVM内存溢出是java编码比较常见的一种性能问题,这种问题在开发和测试环境不容易发现,但是上线初期项目运行一段时间,很容易出现。比较常见于excel导出,非分页大数据量查询等,在工作期间也多次遇到和处理此类问题,分享下定位方法。
内存溢出现象:界面访问突然卡顿,刷新后无法加载出页面。用F12查看,一直处于后端请求直到超时报错;后端查询日志,基本无日志输出,仅仅从日志层面无法看出什么原因导致。
内存溢出原因:大量创建对象,对象处于存活状态,对象在老年代中fullGc无法回收,一直fullgc导致stop the word,程序无法对外提供服务,导致后端无法响应请求。
定位步骤:
1、查询服务器磁盘是否空间不足。
执行df -h命令,查看是否磁盘已满,如果项目所在目录use 100%,则无法打印日志,会导致程序崩溃,如果没有问题,继续往下排查
2、查询服务器内存是否不足
执行free -m命令,查询内存使用情况,如果free基本为0,buff/cache也基本为0,可以判断程序可用内存空间为0,会无法申请到新的内存导致内存溢出。如果正常则继续往下排查
3、查询cpu是否超负荷,超过200%
执行top命令,可以查看下cpu的执行状态,如果%CPU负载超过200%以上,就需要查询cpu超负载的原因,后续会写相关文章定位cpu负载过高定位方法,此不做介绍
此图可以看出33861线程执行了47分钟,为主要的耗费cpu线程,可以断定此线程有比较大的嫌疑,然后继续进行排查
4、查看gc回收是否正常
执行jstat -gcutil pid 1s,查看FGC回收情况,此时会发现FGC的次数一直在增长(大体可以判断一直full gc,无法垃圾回收,内存溢出导致程序崩溃
5、导出堆快照,在服务器上执行jmap -dump:format=b,file=/data/logdir/dump. hprof pid,然后利用工具下载下来进行堆日志分析
6、利用eclipse MemoryAnalyzer分析(推荐),导入堆快照,以下为mat分析截屏
Leak Suspects:展示可能内存溢出的对象,内存溢出一般都是突然大量创建对象导致,根据这个图表可以查看内存最多的前几个对象,一般最多的大多就是导致内存溢出的对象,然后查看调用堆栈信息,直接可以找到相关代码行
7、利用java visual(JDK)自带 ,,导入堆快照,分析如下
发现自定义对象DrpSalesOrder占用了24%的内存,很可能是此实例突然暴增导致jvm无法gc回收,右键“在实例视图中展示”
右键选择“显示最近的垃圾回收根节点”,然后选中elementData,右键“在线程中显示”,如下:
可以发现DrpSalesOrderInvoiceController的2216行代码,大量创建了DrpSalesOrder对象,导致full gc无法回收,系统崩溃。
8、建议:
新项目上线的时候,很多时候会存在性能问题,建议上线初期配置jvm的gc回收日志和内存溢出堆打印快照参数,这样在系统崩溃时可以迅速定位,进行处理。
某配置如下(设置堆大小4G,年轻代2G,元空间初始和最大值256M,使用G1回收算法,开启内存溢出打印堆快照,开启GC回收日志打印):
JAVA_OPTS="-server -Xms4096m -Xmx4096m -Xmn2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m ‐XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/gcdir/dump -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:/gcdir/log/gc.log"
有人问起过jvm调优,调到什么时候才算合理,这个业界也没有给出明确答案。根据多次调优经验,个人认为:年轻代不低于10S回收一次,每次不超过100ms,老年代不低于300S回收一次,每次不超过500ms,即可。这个最终还是要根据具体情况,如果以响应为主,也许要求中断时间更低;以计算为主,不要求响应及时,也许可以接收fullGC回收超过500ms,甚至1S,只要可以接受即可。