【程序大侠传】应用内存缓步攀升,告警如影随形

前序

在武侠编码的江湖中,内存泄漏犹如隐秘杀手,潜伏于应用程序的各个角落,悄无声息地吞噬着系统资源。若不及时发现和解决,必将导致内存枯竭,应用崩溃。

背景:内存泄漏的由来
内存泄漏,乃程序运行过程中,已不再使用的内存块未被及时回收,导致内存使用量不断增加的现象。此问题多发于对象生命周期管理不当之处,如持有对象引用过长,或未能及时释放资源,终致内存枯竭,系统崩溃。

在JVM的世界中,内存泄漏常见于以下几种情况:

  • 静态集合类:如 HashMap、ArrayList 等,若不断向其添加对象而不清理,易造成内存泄漏。
  • 长生命周期对象持有短生命周期对象引用:如单例模式中的对象持有临时对象引用,导致临时对象无法被垃圾回收。
  • 未关闭的资源:如数据库连接、文件流等,若未及时关闭,亦会导致内存泄漏。
  • 监听器与回调函数:未及时移除的监听器或回调函数,可能导致对象无法被回收。

解决方案:内存泄漏的破解之道

  1. 善用工具,探查隐患
    如同侠客需借助兵器,程序员亦需运用内存分析工具,如 jvisualvm、jmap、jhat 等,探查内存使用情况,定位内存泄漏之源。常规步骤如下:
    • 使用 jmap 生成堆转储文件:jmap -dump:live,format=b,file=heap_dump.hprof <pid>
    • 使用 jvisualvm 或 Eclipse MAT 分析堆转储文件,查找无法回收的对象。
  1. 优化代码,清理内存
    针对发现的内存泄漏问题,需优化代码,确保对象在不再使用时尽快释放。具体方法如下:
    • 及时清理集合:对于使用完毕的对象,及时从集合中移除。
    • 合理管理对象引用:避免长生命周期对象持有短生命周期对象引用,可使用弱引用(WeakReference)来管理。
    • 关闭资源:对于文件流、数据库连接等资源,在使用完毕后,务必调用 close() 方法关闭。
    • 移除监听器:在适当时机,移除不再需要的监听器或回调函数。
      加强监控,防患未然

如同江湖侠客需时刻警惕,程序员亦需持续监控内存使用情况,防患于未然。可使用监控工具如 Prometheus、Grafana 等,实时查看内存使用情况,及时发现异常。

滴滴滴、滴滴滴....,代码剑宗中的某一处洞府中的告警声不绝于耳,近眼望去,洞府正中央的蒲团上正坐着一位双眼紧闭身材健硕的男子,此男子的旁边还摆放着篮球、杠铃等道具,从洞府的摆布不难看出此男子平日经常锻炼,以至于他的身型较于常人更加挺拔高大。男子听到告警声睁开双眼,男子双眸中充满精光,看来此次闭关男子有了不少收获。听到警告声的男子眼神中闪过了一丝不耐烦,嘴里轻轻碎了一声,然后不紧不慢地从袖中拿出一个圆盘,此圆盘此时一直闪烁着红光,并且一直发出“滴滴滴”的声音,男子用手轻抚手中圆盘,身前映射出一个巨大光影,光影里面有一些画面跟文字,男子大概花了一刻钟时间扫描完光影里的内容。他脸上闪过一丝苦涩,然后说道:“该死的,竟然出现了内存告警”,随即只见男子双手一挥把光影打散,男子站了起来朝着旁边的一个房间走去。

而洞府中的男子就是咱们的男主“阿强”。他之前正坐在蒲团上修炼内功到一个关键时刻,不曾想被圆盘的告警打断,此时的阿强心情不是很美丽.......

内存紧急处理

阿强离开洞府的第一件事情就是通过告警身份牌进入“乾坤内存法阵”查看告警的应用阵脚,阿强查看应用的内存情况跟系统的一些指标如下图所示,阿强看到这个内存水位情况就发现了不对劲。应用从晚上03:00开始到目前为止内存一直处于一个缓慢上升状态。不过此时阿强倒也没有因此慌了自己阵脚,阿强根据以前处理类似问题的经验,他先通过“乾坤内存法阵”中的应用内存Dump导出功能先将内存快照给dump下来,然后就将应用的容器进行重启的操作。

实时区间热点图
[图片上传失败...(image-83d0fd-1727321763931)]

实时线程数
[图片上传失败...(image-adfc78-1727321763931)]
实时gc数量
[图片上传失败...(image-f145ab-1727321763931)]
实时堆内存信息
[图片上传失败...(image-1a11c0-1727321763931)]
容器实时内存情况
[图片上传失败...(image-407905-1727321763931)]
不久,应用的快照文件就dump了下来,阿强看着dump下来的文件并没有直接去分析而是优先去询问了负责此应用的人询问了一下具体情况。2个时辰后,阿强大概从负责此应用的人口中知道了此应用的基本情况。此应用名叫G服务,是从F服务中拆出来的一个应用,拆出来的G服务的代码内容与G服务是保持一致的,但是G服务的内存表现很稳定,并没有F服务表现出来的内存缓慢爬升的情况,而G服务表现内存缓慢爬升则是随着不断提高流量灰度的一同上升的。其中F服务的一个容器内存情况大致是8台内存16G的云服务器,G服务的容器内存情况是4台8G的云服务器。还有一个值得注意的一个点则是G服务的调用链路由于处于流量切换的过程与在F服务中不同,其区别如:
[图片上传失败...(image-92d011-1727321763931)]
其中橙色的线表示G服务从F服务拆分出来后多一次交互,也就是说,在流量切换灰度期间,G服务的流量入口是从F服务通过rpc接口方式接受的。

此时的阿强大概了解了G服务应用的基本情况,接下来要做的事情则是去分析内存缓慢爬升的问题,只见他拿出了一法器,此法器名叫“乾坤内存镜”,此法器的作用就是能够清晰地分析应用内存快照文文件,在使用法器有一个细节问题需要注意的是,如果通过此法器直接去打开内存快照文件,此法器会默认进行fullgc,fullgc后的快照文件如下图:
[图片上传失败...(image-a7acf0-1727321763931)]
fullgc后的快照文件内存大小明显和实际占用不同,如果想让法器打开快照文件不尽兴fullgc,则只需要换一种打开方式,打开方式如下:
[图片上传失败...(image-49b3ea-1727321763931)]
[图片上传失败...(image-2b658f-1727321763931)][图片上传失败...(image-8c092-1727321763931)]
[图片上传失败...(image-a51259-1727321763931)]
此时你应该能看到如下图的内容说明此次打开方式没有尽兴fullgc,我们只需要稍等片刻即可
[图片上传失败...(image-3a5d83-1727321763931)]
通过这种方式打开的快照文件则是如下所示:
[图片上传失败...(image-d7254c-1727321763931)]
阿强看着解析出来的快照内容,此时展现出来内容是通过内存的实例的数量来进行的排序,其中,char[]占用了1412m大小的内存,粗略看下来没有什么大对象。如果是几年前的阿强,他会傻不拉几地去查看char[]实例的引用,但是此时的阿强已经不是两年前的阿强,经过时长两年半的练习,他踩过数不清的坑,经验告诉他,此时你应该看看第三个实例,阿强此时查看第三个实例的Merged outgoing references,他看到此实例的引用
[图片上传失败...(image-e245da-1727321763931)]
然后再进一步跟进String的引用,除了spring的常规引用,发现logback和jackson有引用大量的字符实例。
[图片上传失败...(image-a6212b-1727321763931)]
[图片上传失败...(image-6d5dff-1727321763931)]
阿强此时通过idea打开了G服务的代码,开始查看起来jackson和logback的代码使用点,2个时辰后,阿强发现了一些奇怪的日志打印,如下:

log.info("业务接口处理请求参数明文,{}, http request method:{}",
                JSON.toJSONString(request.getParams()), methodMapping.getMethod());

上面这种日志打印会将整个请求的入参都打印出来,而且一次请求类似这种打印全部请求入参的日日志大概有5~7次,而由于G服务的承载的业务请求报文都是比较大的,也就是说,每一次请求过来,这种大日子的打印会打印好几次,而这些大而全的日志大部分内容是没用的,并且每次打印生成的字符串每次都是不同的,也就是每次请求在堆内存中生成6~7个大字符对象,这种大字符对象会在堆中频繁创建,会造成youngc很频繁。而youngc过于频繁会造成很多大字符对象进入老年代,导致整个堆内存不断上升。为了验证自己的猜想,阿强尝试着删除G服务中这些大日志的打印,最终发现内存上升的情况有一定的改善(此时的内存已经不会出现缓慢爬坡的情况),但是内存表现相比较F服务还是没有那么好的,因此阿强又只能进一步去分析内存块照文件,2刻钟后,阿强在线程ThreadLocal中发现很多大char[]数组的引用,而这些ThreadLocal都是由rpc线程所持有。
[图片上传失败...(image-67126f-1727321763931)]
而rpc底层的序列化正是使用的jackson,而com.fasterxml.jackson.core.util.BufferRecycler 是 Jackson 库中的一个工具类,用于高效地管理和重用缓冲区。在多线程环境中,特别是使用 ThreadLocal 时,确实有可能导致内存泄漏:

  1. ThreadLocal 的生命周期问题:ThreadLocal 变量会与线程的生命周期绑定,如果线程不被回收,ThreadLocal 变量及其引用的对象也不会被回收,可能导致内存泄漏。
  2. 缓冲区的大小和数量:如果缓冲区的大小或数量非常大,且这些缓冲区长期占用内存而不被释放,可能导致内存使用过多,从而引发内存泄漏。
  3. 线程池使用不当:在使用线程池时,如果没有正确管理线程池的生命周期和资源,可能会导致线程无法被回收,进而导致 ThreadLocal 引用的对象无法被回收。

到这里真相大白,而阿强面对涉及基础设施的改造,他有点烦躁。凡是涉及基础设施的改动,任务的难度和解决时间就会成倍增加,因为基础设施的改造流程会拉的比较长。但这个任务是一个紧急的任务,为了快速地将问题处理,那怎么能够不去改造基础设施并解决这个问题呢,阿强脑子在飞速的运转,不多时,阿强心中闪过一丝光亮,他紧皱的眉间也开始舒坦。刚刚的那一丝光亮就是快速解决任务的关键,那就是:“类加载器的双亲委派机制!!”

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容