《深入理解Java虚拟机》(八) 记录一次OOM问题分析实战
一、问题分析思路
1.考虑一个问题现象,系统刚启动在访问量比较小的时候运行流畅,随着访问量提高,开始卡顿;
2.可以先排查数据库问题,例如Oracle可以检查临时表空间,慢sql统计,索引等等因素,如果依然无法解决;
3.最终再考虑JVM调优,正常情况下是不需要JVM调优的,个人以为,JVM调优更多是针对比较极端的情景的。
4.为什么会系统卡顿? 结合个人见过的问题,一般都是堆上发生的GC,导致系统停顿时间过长,或者过于频繁进行GC,它们都会造成性能损耗,不用猜,此时一般都是在OOM问题的边缘来回试探了。
5.OOM问题表现:
内存泄露
比如出现异常或者代码bug,某些对象产生后就不能被回收。已知:一般情况下,对象在新生代上分配,如果多次收集后依然存活则进入老年代(大对象会直接进入老年代)。不能被回收的对象,会不断占用老年代空间 最终,老年代内存空间占满,新生代对象需要晋升时无法请求到足够内存空间,进入Full GC环节。
性能较差代码导致大量对象无法被快速回收
该问题表现为,在堆内存空间还有剩余时,假设堆上限8G,只要还剩余一定空间时,例如:几百M、或者1G,手动触发Full GC,老年代就会被回收掉,但是放任不管,最终肯定会因为对象分配速度过快导致堆内存占满。
PS *
- Full GC (STW操作,所有用户线程停顿)
- Minor GC/Young GC–目标是新生代的垃圾收集,新生代空间不足时触发,新生代中对象根据存活次数判定是否晋升
- Major GC/Old GC–目标是老年代的垃圾收集
- Mixed GC --目标是整个新生代和部分老年代的垃圾收集(只有G1垃圾收集器存在该行为)。
其实很好理解:Full GC是较难触发的(可以自行了解Full GC触发条件),一般都是较为极端的情况才会触发Full GC;
区别是:
- full gc频率极低,且full gc 能回收掉大量资源,那么你是个好人,你可以走了;
- full gc频率高,并且不能有效回收内存空间,那么可以证明你不对劲,要把你抓起来。一般会指向OOM问题
理解为:我大意了,我没有闪,直到最后知道Full GC到来的我眼泪掉下来。。。。。
PS* 此时或许确实有许多对象可以被回收,但是了解回收算法的人都应该需要知道:
- G1的标记整理算法,需要额外有剩余空间才能进行对象的整理,如果堆空间占满了,那么能整理到哪里去呢?此时Full GC就代表败北,你尽管去进行Full GC,能成功算我输。。。
- CMS收集器, 标记-清除算法,碎片化问题,如果无法找到足够的连续内存空间分配某个对象,那么就会一直进行full GC。。。。
- 除了这俩,应该不会有人再用更古老的收集器了吧?如果有可以看我另一篇博客: 深入理解Java虚拟机》(二) GC 垃圾回收机制
有如下对话为证
- 堆:我真的一滴都没有了
- 但是分配内存的请求就像磨人的小妖精表示:我不管,你给我去挤,你去找
- 于是乎,堆上就会一遍又一遍进行垃圾回收以期待能挤出那么一滴内存空间
- 但是此时堆的体量非常大,每次回收计算引用关系、对象存活信息会非常耗时
- 表现为,系统时时刻刻都会进行非常耗时full gc (stop the work!!!)
- 到了最后绝望的死在了xx的肚皮上
二、主要问题概述以及分析
上班高峰期间,系统使用一段时间后崩溃,报错内存溢出;
1.相关操作
打印系统运行GC日志,初步分析堆内存分代情况;jvisualvm实时观察系统运行过程中堆内存占用、活动线程数量曲线。
2.主要问题现象
- GC日志结合jvisualvm,发现当活动线程数量开始飙升,老年代内存占用飙升;
- 当堆内存空间尚未占满时,手动点击垃圾回收,老年代能被回收,不再出现内存溢出现象


3.初步分析问题
- 结合现象推测,某些操作耗时且频繁,导致老年代扩充速度快于堆内存回收速度,最终 内存溢出。
- 处理:使用堆快照分析工具Jprofile查找问题位置,并处理;
三、相关工具介绍
运行环境:JDK 1.8
垃圾收集器:G1
JvisualVM, JDK自带分析工具,对运行中的程序影响较小,可以实时监控5
JProfile简介,

JProfile详细简介可以看我转载的这篇文章:
https://blog.csdn.net/bokerr/article/details/114445489
四、实际问题快照分析
PS * 由于是公司环境,有些日志就不能提供了
1.通过Memory查看老年代内存占用情况

观察老年代曲线,如果该曲线持续攀升,则观察堆快照。
2.选择Live Memory 视图,
选择Recorded Objects 观察堆内存活对象

点击Mark Current Values记录某一时刻存活的所有对象,并与实时存活兑现数量对比

根据size关键字,找到堆内占据最多内存空间的对象,或者多次GC操作后,实例数
不减反增的对象;
3.生成堆快照,分析对象信息

以String为例,当前快照对比上一步记录的对象实例数,增加了36767个String类对象

Use -> selected instance,根据如图方式追踪对象的产生

选择Allocations 视图追踪相关方法执行堆栈信息

最终分析该方法代码,定位问题。
五、代码逻辑问题
经过分析堆快照,发现问题:
系统里有一个提供给第三方终端设备使用的接口性能较差,查询非常耗时,且对方调用频率过于频繁,最终通过使用缓存技术解决了这个问题。
经过排查发现一个历史代码遗留的BUG,某些情景下方法入参为空时,导致了一个SQL查整表。
六、性能问题
1.问题现象
处理完上述问题后,项目上又一次反馈系统卡顿;如图:内存占用较低,不存在内存即将溢出问题,经过排查,堆内也未发现异常对象

然后,留意到垃圾回收曲线已经变成波浪线,再次查看GC日志发现:
- eden区满,频繁新生代GC
- 新生代共计不足500M内存占用,回收耗时超过一秒
- 垃圾回收过程中Ref Proc,处理各种引用耗时近似垃圾回收耗时

(模拟日志,体现Ref Proc耗时情况)
2.问题处理方法
(G1垃圾收集器)
-XX:+ParallelRefProcEnabled 开启并行处理各种引用
-XX:ParallelGCThreads=8 并行垃圾回收处理线程数
-XX:ConcGCThreads=3 并发标记线程数 ,约等于 ParallelGCThreads / 4
假设,cpu核心数 = X

3.处理结果
处理后GC曲线:

JVM运行状况:
平均15分钟内2~3 次新生代GC,每次GC停顿时间低于10 毫秒
《深入理解Java虚拟机》(八) 记录一次OOM问题分析实战的更多相关文章
- 深入理解Java虚拟机(八)——类加载机制
是什么是类加载机制 Java虚拟机将class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程就是类加载机制. 类的生命周期 一个类从加载到内存 ...
- jvm--深入理解java虚拟机 精华总结(面试)(转)
深入理解java虚拟机 精华总结(面试)(转) 原文地址:http://www.cnblogs.com/prayers/p/5515245.html 一.运行时数据区域 3 1.1 程序计数器 3 1 ...
- 2018.4.23 深入理解java虚拟机(转)
深入理解java虚拟机 精华总结(面试) 一.运行时数据区域 Java虚拟机管理的内存包括几个运行时数据内存:方法区.虚拟机栈.本地方法栈.堆.程序计数器,其中方法区和堆是由线程共享的数据区,其他几个 ...
- 深入理解java虚拟机---java内存区域与内存溢出异常---1内存结构
本文来源于翁舒航的博客,点击即可跳转原文观看!!!(被转载或者拷贝走的内容可能缺失图片.视频等原文的内容) 若网站将链接屏蔽,可直接拷贝原文链接到地址栏跳转观看,原文链接:https://www.cn ...
- 《深入理解 java虚拟机》学习笔记
java内存区域详解 以下内容参考自<深入理解 java虚拟机 JVM高级特性与最佳实践>,其中图片大多取自网络与本书,以供学习和参考.
- 深入理解java虚拟机(1)------内存区域与内存溢出
在C++领域,关于C++的内存存储,结构等等,有一本书:深度探索C++对象模型,讲解的非常透彻. 而Java确把这一工作交给了虚拟机来处理. 我们首先来看看关于内存的问题. 1.问题: 1)java ...
- 深入理解java虚拟机系列(一):java内存区域与内存溢出异常
文章主要是阅读<深入理解java虚拟机:JVM高级特性与最佳实践>第二章:Java内存区域与内存溢出异常 的一些笔记以及概括. 好了開始.假设有什么错误或者遗漏,欢迎指出. 一.概述 先上 ...
- 深入理解Java虚拟机--中
深入理解Java虚拟机--中 第6章 类文件结构 6.2 无关性的基石 无关性的基石:有许多可以运行在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码(ByteCode),从而 ...
- 深入理解Java虚拟机--上
深入理解Java虚拟机--上 第2章 Java内存区域和内存溢出异常 2.2 运行时数据区域 图 2-1 Java虚拟机运行时数据区 2.2.1 程序计数器 程序计数器可以看作是当前线程所执行的字节码 ...
- 深入理解Java虚拟机---学习感悟以及笔记
一.为什么要学习Java虚拟机? 这里我们使用举例来说明为什么要学习Java虚拟机,其实这个问题就和为什么要学习数据结构和算法是一个道理,工欲善其事,必先利其器.曾经的我经常害怕处理内存溢 ...
随机推荐
- [转帖]解决Java/MySQL性能问题的思路
https://plantegg.github.io/2023/08/28/%E8%A7%A3%E5%86%B3%E9%97%AE%E9%A2%98%E6%80%9D%E8%B7%AF/ 10年前写的 ...
- [转帖]Oracle如何重启mmon/mmnl进程(AWR自动采集)
https://www.cnblogs.com/jyzhao/p/10119854.html 学习一下 环境:Oracle 11.2.0.4 RAC现象:sysaux空间满导致无法正常生成快照,清理空 ...
- [转帖]DBWR与LGWR的写入机制
https://www.jianshu.com/p/6c87cb6cd320 读与写是每个数据库提供的最基本的功能.当数据库中出现第一个进程时,总免不了要将数据从磁盘上加载到内存中,一次数据库的物理I ...
- [转帖]Linux系统awk命令详解
AWK 是一种处理文本文件的语言,是一个强大的文本分析工具. 之所以叫 AWK 是因为其取了三位创始人 Alfred Aho,Peter Weinberger, 和 Brian Kernighan 的 ...
- [转帖]exportfs命令
https://www.cnblogs.com/xzlive/p/9766388.html exportfs命令:功能说明 :NFS共享管理语法格式exportfs [必要参数][选择参数][目录]功 ...
- [转帖]linux 查看CPU 内存的信息
https://bbs.huaweicloud.com/blogs/302929 [摘要] ECS信息规格:2vCPUs | 4GiB | kc1.large.2镜像:openEuler 20.0 ...
- 你不知道的Promise构造函数Promise(excutor)
Promise构造函数Promise(excutor) // 说明一下:excutor会在Promise内部立刻同步调用:(异步操作在执行器执行) var p = new Promise((resol ...
- 让一段代码执行在new Vue之前
这是一个自调用函数,也有人叫做一次性函数: 这样函数前面最后打一个: ;(function initApp(){ loadApp(); })() function loadApp (){ //tena ...
- C#中DataTable数据导出为HTML格式文件
/// <summary> /// DataTable导出为HTML的Table并保存到本地 /// </summary> /// <param name="d ...
- 轻量级按键动作识别模块(C语言)
1.前言 继嵌入式(单片机)裸机 C 语言开发 + 按键扫描(模块分层/非阻塞式)文章后,原来的按键识别基本能满足大部分需求,但是对于双击和多击等多样化的功能需求并不能满足,因此对整个按键动作识别模块 ...