一次线上Java应用内存泄漏分析实例

简介: 由于JVM的内存管理采用GC垃圾自动回收机制,这使得Java程序员在编程的时候确实可以从内存管理中释放出来,但这也引发了另外一个大问题,一旦Java应用出现内存泄漏的时候,常常让人措手不及,陷入无从下手的尴尬境地,我们总不能一句话重启吧(苦笑)。

由于JVM的内存管理采用GC垃圾自动回收机制,这使得Java程序员在编程的时候确实可以从内存管理中释放出来,但这也引发了另外一个大问题,一旦Java应用出现内存泄漏的时候,常常让人措手不及,陷入无从下手的尴尬境地,我们总不能一句话重启吧(苦笑)。

内存问题可能是大多数Java程序员心中都曾有过的伤,因为太容易遇见了

下面这篇文章是我去年写在个人微信公众号上的,今天早上回头再看心中不免有些嘘嘘感慨,于是决定重新整理把它发到简书上来,以下为正文:

前2天负责的一个线上系统在早上9点20分接到大量用户反馈系统很卡,页面无响应。我联系运维及时查看后台服务器,发现WAS(Websphere) server bin目录下生成了javacore 和heapdump,这个消息让我整个人都不好了,项目推广关键时期居然来这么一出,可谓多事之秋,搞事情啊!

 好吧,既然程序运行时遇到了坑,那我们就先来找到这个坑,再把坑填好(围笑)。

 简单介绍下,线上应用运行在power linux OS平台之上,web容器为IBM WebSphere ,JDK  ibm j9vm 64 位。

 首先开始通过ibm ha456.jar 分析线上dump 文件

img_9302e3fafc1050ca8724065917023b79.jpe
img_e9acd7ff115aee2fcb8c4d8c4f5f3885.jpe

 上图Heap usage,堆内存已使用约3.7G,且41.95%的内存都被rootobject 和它的引用吃掉了,接下来我再查看rootobject ,如下图:

img_a174437998895c073e4101abc6214e56.jpe

内存主要是被2个LinkedHashMap吃掉了,于是仔细查找项目源码中会用到LinkedHashMap的地方,无果。

接下来再来看一下这些LinkedHashMap当中都包含什么对象,看看它们会指向哪些东西呢,如下图:

img_55fa8bdd86bb7b90364a90d7d23d4027.jpe

这里发现有一个org.xhtmlrender.resource.ImageResource类,且点开所有节点LinkedHashMap 都有此类的实例及强引用,如下图显示第一个root  LinkedHashMap 有178个此类实例

img_084929f368dcaead0255f47a65fd8b66.jpe

于是继续分析下ImageResource 类节点,如下图:

img_98206c411085ea9c99c0e9e6fb9492fd.jpe

此对象节点只有一个parentes node,且这个parentes node 对象就是LinkedHashMap

接着我查看项目源码,发现 org.xhtmlrender.resource.ImageResource类属于如下图:

img_f3e0e94766b48416d1cac037b57e6978.jpe

Maven 引用如下:

img_285e1da471722f565a105251348319fb.jpe

分析项目代码,上面3个jar包是用来将html文件转成pdf文件,目前能推断出的结论是此功能模块耗用尽了jvm 堆内存,由此导致线上应用崩溃了。。。

仔细检查这一功能块区域的代码发现不存在有大规模瞬时加载大量数据到内存中的潜在代码,由此判断不是瞬时某个交易功能导致,应该是此功能区域的某个方法执行时存在内存泄漏的可能

线上应用架构是集群 F5部署,因此有另外一台尚没有生成javacore 的was服务做对比,于是在当晚非作业时间,kill -3 [pid]生成dump 文件进一步分析,发现这台应用的堆内存也用到3.6G,只是还没有到崩溃的临界点而已,且都是被同样的LinkedHashMap 吃掉大半以上。

内存泄漏点几乎已确定在此处了,于是让同事写了一个客户端测试小程序并发的对此功能区方法Action发起请求,以下为测试环境请求我自己本地服务JVM监控图示:

img_19fb62366a275b1baa4ee2b25109adba.jpe
1

上部左边图可看出垃圾回收活动越来越频繁,上部右边图堆内存的已使用占比也在缓慢递增,典型的内存有泄漏,GC每次回收都不完全,导致GC回收的复杂度越来越高,离out of memory 只是时间问题。

再对测试环境中的was服务器进行压测,以进一步验证此问题点,发现was体系内存的开销更明显,如下图,看到内存呈现阶梯式递增

img_e957a3e5f91fde909b923b174bb4f52c.jpe
2

对比上面2图,在客户端测试程序压力一定情况下,was服务器(1.6jdk)的表现比我本机还要糟糕,这可能是我本机Mac用的1.8 jdk JVM GC相对于1.6有优化吧。

服务端在运行4小时之后,内存耗尽,程序Over,且在was应用目录下生成了javacore(这期间暂停过很多次客户端压力程序,但服务端JVM堆内存的占用一直是居高不下,无法回收)。

img_4aa7981e54bdfaf745fb43806022a3b3.png
3

同样使用ha456分析javacore,发现堆内存的耗用情况与生产几乎一致,堆内存大部分都是耗用在LinkedHashMap和commons pool中


img_249647a718381560cd59eb8197e52191.png
4

至此断定问题一定是出在html转pdf方法体中,查阅项目源码及用到的开源jar包说明文档,定位到出问题的关键代码块如下:

img_793163e06dc62aadad0108f4bbc63e67.jpe

初步分析应该是ITextRenderObjctFactory中ObjctPool池的ITextRender对象资源不释放导致的,对象池Factory关键配置如下:

img_fa2013b029e4917c374a78f2004571d8.jpe

查阅相关资料说明,我分析应该是ITextRende对象一直活跃在对象池pool中(pool默认配置:空闲对象可存活30分钟),应用在白天作业期间对htmlToPdf方法的调用几乎不会有空闲时间,所以render一直处于被外围重复调用的情况,导致render持有的资源一直无法得到释放的机会,进而使程序内存耗尽

调研过其他项目组源码也有类似实现的写法,但是未出现内存耗尽的情况,对比源码分析区别在于其他项目组render渲染html为pdf 时并未涉及到html文件中有可变图片url引用的链入,而我们项目组每次html转pdf 时存在可变路径的图片引用,导致render需要每次动态创建ImageResource类来动态加载外部图片URL链接,而动态创建的ImageResource 实例并未能释放。

因此对以上关键代码进行优化如下:

img_772a5e69880cd22972ee84969dfe1afb.jpe

第一个红色箭头修改部分改为document 由原来的url加载方式改为file

第二个红色箭头为新添加代码,用于把ITextRender共享上下文Context内容重置,以便于单个render对象中的资源及时释放;

同时对于ObjectPool池config配置优化如下,关键代码:

img_2f544173b2a54d68e0c74618ff721067.jpe

修改参数说明如下:

1、空闲对象获取策略修改为FIFO,先进先出,便于对象池中的对象公平共享使用,便于空闲未使用到的对象及时释放


img_fc6ceddf4856a5df0f3dcee9d49942b9.jpe

2、timeBetweenEvictionRunsMillis为每间隔5秒检查一下是否存在空闲Idle对象

3、minEvictableIdleTimeMillis空闲对象存在1分钟未被调用即销毁

OK,优化之后,更新was服务,使用之前的客户端测试程序发起压力测试,将堆内存上限设定为1G,结果表现如下:


img_2ec80c273e8302fb287129d355c90377.jpe

持续压测2小时,内存耗用稳定在1G以内,当客户端压力撤去,3分钟之后内存稳稳回收到200M左右,如下:


img_8ea3e91d7596dfa888a3dfc5f5863329.jpe

接下来稳定性测试6小时,持续压测,压力撤去之后,内存占用逐渐回归到600M左右


img_95900b048a54a6720aa82234778bcdbe.jpe

更新线上应用程序不再出现问题,至此,总算把此问题解决。在这期间收集用于分析的javacore和dump文件约5G(包含测试环境was、生产环境was、本机tomcat)

img_23361ef227c1e71ad0394fb03f40b1e5.jpe

以上从定为问题、找到原因、测试复现、解决问题到复测通过我断断续续用了2天时间,这2天放下了手头一切工作上相对来说优先级没有此问题高的事情,可能有的朋友会感觉我最近工作不走心了呢,其实不然,不会了(围笑)。

软件系统最常见的就是系统故障,人非圣贤孰能无过,程序员都是人,是人就会写Bug,写了Bug系统就会出问题,出了问题系统就可能宕机。即使你什么事都没干正在家里陪老丈人喝酒,人家在微博上说,大家好,给大家介绍一下,这是我的女盆友。哐当,系统挂了,这特么能怪谁呢?真出了问题,慌,骂人,处罚都是后话,没用的,重要的是解决问题,梳理流程,增加监控,减少损失,最后复盘问题才是牛批。

现在我们的项目正处于大力推广前的关键时期,软件将迎来最终用户的快速增长,保持服务的稳定是重中之重。

前段时间看到Mac Talk公众号池建强老师发的一篇原创文章,大意讲的是我们每个人在临大战前应有静气,何意?就是你面临压力和挑战的时候,一方面心跳加速血管收缩手心冒汗,但你感到的是兴奋而不是焦虑,内心深处是安静的,沉着的。

最后大家要是觉得本文还OK,对你的工作学习有所帮助的话请给点个赞️!笔芯

如果你对设计模式学习感兴趣的话,请关注下我,因为我最近正在编写一部《从零开始学设计模式》系列。

目录
相关文章
|
7天前
|
存储 Java 编译器
Java内存区域详解
Java内存区域详解
21 0
Java内存区域详解
|
1天前
|
Java 关系型数据库 MySQL
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
|
1天前
|
设计模式 算法 Java
Java中的设计模式及其应用
【4月更文挑战第18天】本文介绍了Java设计模式的重要性及分类,包括创建型、结构型和行为型模式。创建型模式如单例、工厂方法用于对象创建;结构型模式如适配器、组合关注对象组合;行为型模式如策略、观察者关注对象交互。文中还举例说明了单例模式在配置管理器中的应用,工厂方法在图形编辑器中的使用,以及策略模式在电商折扣计算中的实践。设计模式能提升代码可读性、可维护性和可扩展性,是Java开发者的必备知识。
|
1天前
|
安全 Java API
函数式编程在Java中的应用
【4月更文挑战第18天】本文介绍了函数式编程的核心概念,包括不可变性、纯函数、高阶函数和函数组合,并展示了Java 8如何通过Lambda表达式、Stream API、Optional类和函数式接口支持函数式编程。通过实际应用案例,阐述了函数式编程在集合处理、并发编程和错误处理中的应用。结论指出,函数式编程能提升Java代码的质量和可维护性,随着Java语言的演进,函数式特性将更加丰富。
|
2天前
|
移动开发 Android开发 开发者
构建高效Android应用:采用Kotlin进行内存优化的策略
【4月更文挑战第18天】 在移动开发领域,性能优化一直是开发者关注的焦点。特别是对于Android应用而言,由于设备和版本的多样性,确保应用流畅运行且占用资源少是一大挑战。本文将探讨使用Kotlin语言开发Android应用时,如何通过内存优化来提升应用性能。我们将从减少不必要的对象创建、合理使用数据结构、避免内存泄漏等方面入手,提供实用的代码示例和最佳实践,帮助开发者构建更加高效的Android应用。
5 0
|
3天前
|
Java API 数据库
深入解析:使用JPA进行Java对象关系映射的实践与应用
【4月更文挑战第17天】Java Persistence API (JPA) 是Java EE中的ORM规范,简化数据库操作,让开发者以面向对象方式处理数据,提高效率和代码可读性。它定义了Java对象与数据库表的映射,通过@Entity等注解标记实体类,如User类映射到users表。JPA提供持久化上下文和EntityManager,管理对象生命周期,支持Criteria API和JPQL进行数据库查询。同时,JPA包含事务管理功能,保证数据一致性。使用JPA能降低开发复杂性,但需根据项目需求灵活应用,结合框架如Spring Data JPA,进一步提升开发便捷性。
|
3天前
|
缓存 移动开发 Java
构建高效的Android应用:内存优化策略
【4月更文挑战第16天】 在移动开发领域,尤其是针对资源有限的Android设备,内存优化是提升应用性能和用户体验的关键因素。本文将深入探讨Android应用的内存管理机制,分析常见的内存泄漏问题,并提出一系列实用的内存优化技巧。通过这些策略的实施,开发者可以显著减少应用的内存占用,避免不必要的后台服务,以及提高垃圾回收效率,从而延长设备的电池寿命并确保应用的流畅运行。
|
3天前
|
存储 缓存 监控
Java内存管理:垃圾回收与内存泄漏
【4月更文挑战第16天】本文探讨了Java的内存管理机制,重点在于垃圾回收和内存泄漏。垃圾回收通过标记-清除过程回收无用对象,Java提供了多种GC类型,如Serial、Parallel、CMS和G1。内存泄漏导致内存无法释放,常见原因包括静态集合、监听器、内部类、未关闭资源和缓存。内存泄漏影响性能,可能导致应用崩溃。避免内存泄漏的策略包括代码审查、使用分析工具、合理设计和及时释放资源。理解这些原理对开发高性能Java应用至关重要。
|
7天前
|
Java
探秘jstack:解决Java应用线程问题的利器
探秘jstack:解决Java应用线程问题的利器
15 1
探秘jstack:解决Java应用线程问题的利器
|
7天前
|
Java Shell
Java 21颠覆传统:未命名类与实例Main方法的编码变革
Java 21颠覆传统:未命名类与实例Main方法的编码变革
10 0