深入理解JVM - 案例实战

简介: 深入理解JVM - 案例实战

前言:



这一篇文章还是讲实战,但是内容并不是很多,下一篇会出一个阶段总结对于之前的内容进行回顾。


前文回顾



上一节深入扩展了JVM工具jstat是如何使用了,但是从实际场景可以看出,更多情况是代码的问题,或者因为好奇害死猫乱设置参数导致线上各种报错或者频繁的卡死,这里还是再次强调一句不要使用System.gc()这个臭名昭著的方法,最好是JVM禁止此方法的运行。


本文概述



  1. 排查Full Gc的套路是什么,这里用一个电商案例来进行说明。
  2. spilt()方法是如何造成内存泄露的?如何通过可视化图形分析出问题。以及如何从源代码层面发现根本问题


思维导图:



幕布:www.mubucm.com/doc/IgrEXbw…


网络异常,图片无法展示
|


电商案例-排查Full GC套路



主要业务:


在日常场景进行发邮箱,短信以及APP 的推送消息一些特别活动。

这种业务的特点是短时间之内会有大量的用户进入APP进行参与,这时候系统的压力会突然增加。


问题:


在业务流量高峰的时候,CPU的使用率十分十分高,并且直接导致系统卡死,无法进行任何请求的处理,在系统重启之后会好一段时间,但是后面又会马上卡死。


初步排查:


  • 首先我们需要排查是否为 线程创建过多:线程过多并且并发执行差,所以CPU的上下文切换十分频繁,压力很大
  • 频繁的FULL GC导致系统卡顿

通过这种思路排查,结果果然发现FULL GC的频率十分高,居然一分钟一次FULL GC,频率实在是太高了。


初步排查FULL GC的套路有哪些:(重点)


  1. 内存分配不合理,对象频繁进入老年代,引发频繁FULL GC
  2. 内存泄露问题,内存驻留大量的老年代对象,一有对象就会触发FULL GC,比如之前提到的全表查询引发海量对象
  3. 永久代的类太多,触发 FULL GC


继续排查:


继续排查发现使用jstat发现并不存在内存不合理的情况,并且对象也是正常进入老年代,同时永久代的内存居然也是正常的。

这时候又会考虑一个问题,一分钟一次FULL GC,证明老年代空间是不够的,虽然新生代进入老年代是正常的,但是如果老年代 本身对象就非常多,会不会也会出现问题呢?按照这个思路继续排查,果然发现老年代GC之后 居然还有那么多对象存活

真相大白,原因就是老年代被大量对象占满了,很容易触发FULL GC,我们可以使用Jmap的工具排查这里面的内容,当然,也可以使用mat(memory anaylyze tool)进行排查,但是本文不涉及工具的使用介绍,大致介绍一下mat的处理流程:


MAT的排查进程:
jmap -dump:format=b,file=文件名[服务进程ID]
1. 首先内存快照,可以看到当前内存情况
2. 其次发现内存泄露
3. 创建的对象占比量过大
4. 发现原因是jvm缓存没有及时进行清理,导致内存越来越大
5. 排查结果是本地内存没有进行限制,同时没有定期淘汰算法
6. 解决办法使用一些EHCACASH的缓存即可
复制代码


解决方式:


  1. 使用JSTAT和JMAP找到让对象大量创建的原因
  2. 使用MAT 软件进行分析
  1. jmap -dump:format=b,file=文件名[服务进程ID]
  2. 使用jhat等可视化图形工具进行分析。
  1. 解决代码层面短时间大量创建对象的问题。


总结:


其实按照排查思路进行一步步排查,要找到问题其实并不是很难。


String.split是如何造成内存泄露的


主要业务:


业务就直接跳过,这里重点关注问题分析和解决流程。


问题分析:


  1. 发现也是CPU突然爆高,但是可以看到新生代和老年代居然同时有10G的内存大小
  2. 发现每两分钟就会有一次FULL GC同时伴随着系统的资源高度占用
  3. 不是简单的改一下JVM参数就可以解决的事情,排查发现代码出了问题。

不用说,标题已经暴露了一切,但是究竟是如何分析出来的?这里也不兜圈子,直接给一张图,:


网络异常,图片无法展示
|


Problem Suspect 1


从这里看到java.lang.Thread的主线程main 线程,局部变量居然占用了**24.97%**的内存的对象。这里告诉你问题出现在java.lang.Object[]数组,这个数组占用大量的内存。

在1的下面有一行蓝色的 Details,进入之后可以看到下面的内容:


网络异常,图片无法展示
|


Problem Supspect 3里面也可以看到这里面占用了大量的String对象。

从这里可以看到在main线程里面,有一个arrayList集合占用了几乎所有的内存,这个List显然也是Object[]的数组,并且在内容里面存在Demo1$Data的对象实例。

从这个分析我们知道了如何分析出内存占用的问题,其实大胆猜测加上实用工具测试可以基本都可以验证出问题。


trance链路追踪:


知道了占用是因为Object[]数组的问题,接着来看下链路追踪的情况:


网络异常,图片无法展示
|


如上图所示,我们点击statictrace进入到具体的代码界面:

答案在最下面的图:


网络异常,图片无法展示
|



我们可以明显的看到是String的问题,通过代码搜索发现有一个String.split可能是产生问题的原因。


为什么String.split()会造成内存泄露


这里就涉及一个JDK源代码的问题了:

在JDK6的版本,一个字符串的底层是基于下面的形式进行存储的,比如"yes yes yes yes"使用空格切分是如下的形式:

["yes","yes","yes","yes"]

但是到了Jdk7,他给每个切分出来的字符串都创建了一个新的数组,意思就是说每次切分都切分出一个新的数组,这里可能没法理解,所以我们给出代码:


if (xxxxx)// 一大堆判断,不用管,总之大部分情况你都会进这个If判断
{    
    return list.subList(0, resultSize).toArray(result);
}
return Pattern.compile(regex).split(this, limit);
复制代码


这个sublist毫无疑问就是罪魁祸首了,导致JDK版本升级了之后内存占用爆高也是这个代码,这个代码干了啥呢?


public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}
复制代码


这个也是典型的面试题,可用看到返回了当前List的视图,同时这个视图会随着数组的改变而改变,关于这个对象细节百度一大堆,这里不讨论,这里需要关注的是这个new


到这里相信读者也清楚为什么split()方法会导致大量的Object[]数组被构建出来,SubList底层依然是一个数组!


解决方式:


说白了还是代码的质量问题,不用想可以知道需要从代码层面修复问题,解决fot循环里面的split()方法。

所以字符串的操作尤其需要谨慎,因为字符串天生的不可变的特性,使用频率非常高的同时也很容易出现问题。


总结:


这篇文章内容不多,主要为下面两个点:

  1. 通过可视化工具以及代码排查,可以从分析图表里面看到根本的代码问题点
  2. 关于FULL GC的常见排查讨论


写在最后


感谢各位的观看,下一篇文章为阶段总结。

相关文章
|
6月前
|
监控 Java 调度
探秘Java虚拟机(JVM)性能调优:技术要点与实战策略
【6月更文挑战第30天】**探索JVM性能调优:**关注堆内存配置(Xms, Xmx, XX:NewRatio, XX:SurvivorRatio),选择适合的垃圾收集器(如Parallel, CMS, G1),利用jstat, jmap等工具诊断,解决Full GC问题,实战中结合MAT分析内存泄露。调优是平衡内存占用、延迟和吞吐量的艺术,借助VisualVM等工具提升系统在高负载下的稳定性与效率。
102 1
|
2月前
|
监控 架构师 Java
JVM进阶调优系列(6)一文详解JVM参数与大厂实战调优模板推荐
本文详述了JVM参数的分类及使用方法,包括标准参数、非标准参数和不稳定参数的定义及其应用场景。特别介绍了JVM调优中的关键参数,如堆内存、垃圾回收器和GC日志等配置,并提供了大厂生产环境中常用的调优模板,帮助开发者优化Java应用程序的性能。
|
2月前
|
Java 应用服务中间件 程序员
JVM知识体系学习八:OOM的案例(承接上篇博文,可以作为面试中的案例)
这篇文章通过多个案例深入探讨了Java虚拟机(JVM)中的内存溢出问题,涵盖了堆内存、方法区、直接内存和栈内存溢出的原因、诊断方法和解决方案,并讨论了不同JDK版本垃圾回收器的变化。
32 4
|
2月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
54 2
|
7月前
|
Java 关系型数据库 MySQL
【JVM】JDBC案例打破双亲委派机制
【JVM】JDBC案例打破双亲委派机制
169 4
|
5月前
|
运维 监控 Java
(十)JVM成神路之线上故障排查、性能监控工具分析及各线上问题排错实战
经过前述九章的JVM知识学习后,咱们对于JVM的整体知识体系已经有了全面的认知。但前面的章节中,更多的是停留在理论上进行阐述,而本章节中则更多的会分析JVM的实战操作。
116 1
|
5月前
|
缓存 监控 Java
Java虚拟机(JVM)性能调优实战指南
在追求软件开发卓越的征途中,Java虚拟机(JVM)性能调优是一个不可或缺的环节。本文将通过具体的数据和案例,深入探讨JVM性能调优的理论基础与实践技巧,旨在为广大Java开发者提供一套系统化的性能优化方案。文章首先剖析了JVM内存管理机制的工作原理,然后通过对比分析不同垃圾收集器的适用场景及性能表现,为读者揭示了选择合适垃圾回收策略的数据支持。接下来,结合线程管理和JIT编译优化等高级话题,文章详细阐述了如何利用现代JVM提供的丰富工具进行问题诊断和性能监控。最后,通过实际案例分析,展示了性能调优过程中可能遇到的挑战及应对策略,确保读者能够将理论运用于实践,有效提升Java应用的性能。 【
205 10
|
4月前
|
存储 监控 算法
深入解析JVM内部结构及GC机制的实战应用
深入解析JVM内部结构及GC机制的实战应用
|
5月前
|
监控 Java Linux
Linux下JVM相关指令详解及案例介绍
Linux下JVM相关指令详解及案例介绍
58 1
|
6月前
|
存储 缓存 监控
深入JVM:解析OOM的三大场景,原因及实战解决方案
深入JVM:解析OOM的三大场景,原因及实战解决方案