JVM 调优实战之内存过高问题(七)

调优实战:demo项目介绍:


核心代码介绍:

Linux 服务跑起来
java -cp ref-jvm3.jar -XX:+PrintGC -Xms200M -Xmx200M ex13.FullGCProblem
 
CPU 占用过高排查实战
1. 先通过 top 命令找到消耗 cpu 很高的进程 id 假设是 2732
top 命令是我们在 Linux 下最常用的命令之一,它可以实时显示正在执行进程的 CPU 使用率、内存使用率以及系统负载等信息。其中上半部分显示的是 系统的统计信息,下半部分显示的是进程的使用率统计信息。
 
 
2. 执行 top -p 2732 单独监控该进程
3 、在第 2 步的监控界面输入 H ,获取当前进程下的所有线程信息
 
4 、找到消耗 cpu 特别高的线程编号,假设是 2734 (要等待一阵),转换成16进制;
5 、执行 jstack 2732  对当前的进程做 dump ,输出所有的线程信息;
 
6 将第 4 步得到的线程编号 11354 转成 16 进制是 0x7b
 
也可以通过计算器来换算。
7 根据第 6 步得到的 0x7b 在第 5 步的线程信息里面去找对应线程内容
8 解读线程信息,定位具体代码位置
9、发现找是J VM 的线程占用过高,我们发现我开启的参数中,有垃圾回收的日志显示,所以我们要换一个思路,可能是我们的业务线程没问题,而是垃圾 回收的导致的。 (代码中有打印 GC 参数,生产上可以使用这个 jstat –gc 来统计,达到类似的效果) 是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据,在没有 GUI 图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。 假设需要每 250 毫秒查询一次进程 13616 垃圾收集状况,一共查询 10 次,那命令应当是: jstat-gc 13616 250010
 
 
使用这个大量的 FullGC 了 还抛出了 OUT Of Memory
 
 
S0C :第一个幸存区的大小
S1C :第二个幸存区的大小
S0U :第一个幸存区的使用大小
S1U :第二个幸存区的使用大小
EC :伊甸园区的大小
EU :伊甸园区的使用大小
OC :老年代大小
OU :老年代使用大小
MC :方法区大小
MU :方法区使用大小
CCSC: 压缩类空间大小
CCSU: 压缩类空间使用大小
YGC :年轻代垃圾回收次数
YGCT :年轻代垃圾回收消耗时间
FGC :老年代垃圾回收次数
FGCT :老年代垃圾回收消耗时间
GCT :垃圾回收消耗总时间
怎么办? OOM .
我们可以看到,这个里面 CPU 占用过高是什么导致的? 是业务线程吗?不是的,这个是 GC 线程占用过高导致的。 JVM 在疯狂的进行垃圾回收,再回顾下之前的知识, JVM 中默认的垃圾回收器是多线程的(回 顾下之前的文章),所以多线程在疯狂回收,导致 CPU 占用过高。
10、内存占用过高思路
用于生成堆转储快照(一般称为 heapdump dump 文件)。 jmap 的作用并不仅仅是为了获取 dump 文件,它还可以查询 finalize 执行队列、 Java 堆和永 久代的详细信息,如空间使用率、当前用的是哪种收集器等。和 jinfo 命令一样, jmap 有不少功能在 Windows 平台下都是受限的,除了生成 dump 文件的 -dump 选项和用于查看每个类的实例、
空间占用统计的 -histo 选项 在所有操作系统都提供之外
 
 
JVM 中的对象全部打印出来, 但是这样太多了,那么我们选择前 20 的对象展示出来,
jmap –histo 1196 | head -20
 
 
11、 定位问题的关键,就是这条命令。 很多个 88 万个对象。
 
问题总结(找到问题)
一般来说,前面这几行,就可以看出,到底是哪些对象占用了内存。 这些对象回收不掉吗?是的,这些对象回收不掉,这些对象回收不掉,导致了 FullGC, 里面还有 OutOfMemory
 
 
 
 
任务数多于线程数,那么任务会进入阻塞队列,就是一个队列,你进去,排队,有机会了,你就上来跑。 但是同学们,因为代码中任务数一直多于线程数,所以每 0.1S ,就会有 50 个任务进入阻塞对象, 50 个任务底下有对象,至少对象送进去了,但是没执行。 所以导致对象一直都在,同时还回收不了。
 
为什么回收不了。 Executor 是一个 GCroots
 
 
所以堆中,就会有对象 80 万个,阻塞队列中 80 万个任务, futureTask 。并且这些对象还回收不了。
 
总结
JVM 出现性能问题的时候。(表现上是 CPU100% ,内存一直占用)
1 、 如果 CPU 100% ,要从两个角度出发,一个有可能是业务线程疯狂运行,比如说想很多死循环。还有一种可能性,就是 GC 线程在疯狂的回收,因 为 JVM 中垃圾回收器主流也是多线程的,所以很容易导致 CPU 100%
2 、 在遇到内存溢出的问题的时候,一般情况下我们要查看系统中哪些对象占用得比较多,我的是一个很简单的代码,在实际的业务代码中,找到对应的 对象,分析对应的类,找到为什么这些对象不能回收的原因,就是我们前面讲过的可达性分析算法,JVM 的内存区域,还有垃圾回收器的基础,当然, 如果遇到更加复杂的情况,你要掌握的理论基础远远不止这些(JVM 很多理论都是排查问题的关键)
常见问题分析
超大对象
代码中创建了很多大对象 , 且一直因为被引用不能被回收,这些大对象会进入老年代,导致内存一直被占用,很容易引发 GC 甚至是 OOM
 
超过预期访问量
通常是上游系统请求流量飙升,常见于各类促销 / 秒杀活动,可以结合业务流量指标排查是否有尖状峰值。 比如如果一个系统高峰期的内存需求需要 2 G 的堆空间,但是堆空间设置比较小,导致内存不够,导致 JVM 发起频繁的 GC 甚至 OOM
过多使用 Finalizer
过度使用终结器( Finalizer ),对象没有立即被 GC Finalizer 线程会和我们的主线程进行竞争,不过由于它的优先级较低,获取到的 CPU 时间较少,因此 它永远也赶不上主线程的步伐,程序消耗了所有的可用资源,最后抛出 OutOfMemoryError 异常。
内存泄漏
大量对象引用没有释放, JVM 无法对其自动回收。
长生命周期的对象持有短生命周期对象的引用
例如将 ArrayList 设置为静态变量,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏
连接未关闭
如数据库连接、网络连接和 IO 连接等,只有连接被关闭后,垃圾回收器才会回收对应的对象。
 
 
变量作用域不合理
例如, 1. 一个变量的定义的作用范围大于其使用范围, 2. 如果没有及时地把对象设置为 null
内部类持有外部类
Java 的非静态内部类的这种创建方式,会隐式地持有外部类的引用,而且默认情况下这个引用是强引用,因此,如果内部类的生命周期长于外部类的生命 周期,程序很容易就产生内存泄漏 如果内部类的生命周期 长于 外部类的生命周期,程序很容易就产生内存泄漏(垃圾回收器会回收掉外部类的实例,但由于内部类持有外部类的引用,导 致垃圾回收器不能正常工作)
解决方法:你可以在内部类的内部显示持有一个外部类的软引用 ( 或弱引用 ) ,并通过构造方法的方式传递进来,在内部类的使用过程中,先判断一下外部 类是否被回收;
Hash 值改变
在集合中,如果修改了对象中的那些参与计算哈希值的字段,会导致无法从集合中单独删除当前对象,造成内存泄露(有代码案例 Node 类)
 
内存泄漏经典案例
 
 
 
 
代码问题
代码问题和内存泄漏很大的关系,如果观察一个系统,每次进行 FullGC 发现堆空间回收的比例比较小,尤其是老年代,同时对象越来越多,这个时候可 以判断是有可能发生内存泄漏。
内存泄漏
程序在申请内存后,无法释放已申请的内存空间。
内存泄漏和内存溢出辨析
内存溢出:实实在在的内存空间不足导致;
内存泄漏:该释放的对象没有释放,常见于使用容器保存元素的情况下。
如何避免:
内存溢出:检查代码以及设置足够的空间
内存泄漏:一定是代码有问题
往往很多情况下,内存溢出往往是内存泄漏造成的。
我们一般优化的思路有一个重要的顺序:
1. 程序优化,效果通常非常大;
2. 扩容,如果金钱的成本比较小,不要和自己过不去;
3. 参数调优,在成本、吞吐量、延迟之间找一个平衡点。
 
 
今天的实战调优分析到这里,大家有不懂的地方随时留言,下篇我们利用 MAT 工具 分析内存泄漏,敬请期待!
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寅灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值