我的程序跑了60多小时,就是为了让你看一眼JDK的BUG导致的内存泄漏。 (1)

简介: 我的程序跑了60多小时,就是为了让你看一眼JDK的BUG导致的内存泄漏。 (1)

这次的文章从JDK的J.U.C包下的ConcurrentLinkedQueue队列的一个BUG讲起。jetty框架里面的线程池用到了这个队列,导致了内存泄漏。


同时通过jconsole、VisualVM、jmc这三个可视化监控工具,让你看见“内存泄漏”的发生。有点意思,大家一起看看。


从一个BUG说起


前段时间翻到了一个 JDK 有点意思的 BUG,带大家一起瞅瞅。


https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8137185


memory leak,内存泄漏。


是谁导致的内存泄漏呢?


ConcurrentLinkedQueue,这个队列。


这个 BUG 里面说,在 jetty 项目里面也爆出了这个 BUG:


我看了一下,觉得 jetty 的这个写的挺有意思的。


我按照 jetty 的这个讲吧,反正都是同一个 JDK BUG 导致的。地址如下:


https://bugs.eclipse.org/bugs/show_bug.cgi?id=477817


我用我八级半的蹩脚英语给大家翻译一下这个叫做 max 的同学说了些什么。


他说:在 Java 项目里面,错误的使用 ConcurrentLinkedQueue(文章后面用缩写 CLQ 代替)会导致内存泄漏的问题。


在 jetty 的 QueuedThreadPool 这个线程池里面,使用了 CLQ 这个队列,它会导致内存缓慢增长,最终引发内存泄漏。


虽然 QueuedThreadPool 仅仅使用了这个队列的 add 方法和 remove 方法。但不幸的是,remove 方法不会把队列的大小变小,只会使队列里面被删除的 node 为空。因此,该列表将增长到无穷大。


然后他给了一个附件,附件里面是一段程序,可以演示这个问题。


我们先不看他的程序,后面我们统一演示这个问题。


先给大家看一下 jetty 的 QueuedThreadPool 线程池。


看哪个版本的 jetty 呢?


可以看到这个 BUG 是在 2015 年 9 月 18 日被爆出来的。所以,我们找一个这个日期之前的版本就行。


于是我找了一个 2015 年 9 月 3 日发布的 maven 版本:


在这个版本里面的 QueuedThreadPool 是这样的:


可以看到,它确实使用了 CLQ 队列。


而从这个对象所有被调用的地方来看,jetty 只使用了这个队列的 size、add、

remove(obj) 方法:


和前面 max 同学描述的一致。


然后这个 max 同学给了几张图片,来佐证他的论点:


主要关注我框起来的地方,就是说他展示了一张图片。可以从这图片中看出内存泄漏的问题,而这个图片的来源是他们真实的项目。


这个项目已经运行了大约两天,每五分钟就会有一个 web 请求过来。


下面是他给出的图片:


从他的这个图片中,我就只看出了 CLQ 的 node 很多。


但是他说了,他这个项目请求量并不大,用的 jetty 框架也不应该创建这么多的 node 出来。


好了,我们前面分析了 max 同学说的这个问题,接下来就是大佬出场,来解惑了:


我们先不看回答,先看看回答问题的人是谁。


Greg Wilkins,何许人也?


我找到了他的领英地址:


https://www.linkedin.com/in/gregwilkins/?originalSubdomain=au


jetty 项目的领导者,短短的几个单词,就足以让你直呼牛逼。


高端的食材,往往只需要最简单的烹饪。高端的人才,往往只需要寥寥数语的介绍。


大佬的简历就是这么朴实无华,且枯燥。


而且,你看这个头像。哎,酸了酸了。果然再次印证了这句话:变秃了,也变强了,并不适用于外国的神仙。


好了,我们看一下这个 jetty 项目的领导者是怎么回答这个问题的:


首先他用 stupefied 表示了非常的震惊!然后,用到了 Ouch 语气词。相当于我们常说的:


他说:卧槽,我发现它不仅导致内存泄漏,而且会随着时间的推移,导致队列越来越慢。太TM震惊了。


这个问题一定会对使用大量线程的服务器产生影响......希望不是所有的服务器都会有影响。


但不管是不是所有的服务器都有这个问题,只要出现了这个问题,对于某些服务器来说,它一定是一个非常严重的 BUG。


然后他说了一个 Great catch!我理解这是一个语气助词。就类似于:太牛逼了。


这个不好翻译,我贴一个例句,大家自己去体会一下吧:


我也是没想到,在技术文里面还给大家教起了英文。


最后他说:我正在修复这个问题。


然后,在 7 分 37 秒之后, Greg 又回复了一次:


可以看出,过了快 8 分钟,他还在持续震惊。我怀疑这 8 分钟里面他一直在摇头。


他说:我还在为这个 BUG 摇头,它怎么这么久都没被发现呢!对于 jetty 来说修复起来非常的简单,使用 set 结构代替 queue 队列即可实现一样的效果。


那我们看一下修复之后的 jetty 中的 QueuedThreadPool 是怎样的,这里我用的是 2015 年 10 月 6 日发布的一个包,也就是这个 BUG 爆出之后的最近的一个包:


里面对应的代码是这样的:


简单粗暴的用 CurrentHashSet 代替了 CLQ。


因为这个 BUG 在 JDK 中是已经修复了,出于好奇,我想看看 CLQ 还有没有机会重新站出来。


于是我看了一下今年发布的最新版本里面的代码:


既不是用的 CurrentHashSet ,也没有给 CLQ 机会。


而是 JDK 8 的 ConcurrentHashMap 里面的 newKeySet 方法,C 位出道:


这是一个小小的 jetty 线程池的演变过程。恭喜你,又学到了一个基本上不会用到的知识点。


回到 Greg 的回复中,这次的回复里面,他还给了一个修复的演示实例,下一小节我会针对这个实例进行解读。


在 23 分钟之后,他就提交代码修复完成了。


从第一次回复帖子,到定位问题,再到提交代码,用了 30 分钟的时间。


目录
相关文章
|
3月前
|
安全 Linux Shell
Linux上执行内存中的脚本和程序
【9月更文挑战第3天】在 Linux 系统中,可以通过多种方式执行内存中的脚本和程序:一是使用 `eval` 命令直接执行内存中的脚本内容;二是利用管道将脚本内容传递给 `bash` 解释器执行;三是将编译好的程序复制到 `/dev/shm` 并执行。这些方法虽便捷,但也需谨慎操作以避免安全风险。
196 6
|
21天前
|
监控 Oracle Java
JDK 21中的分代ZGC:一场内存管理的革命
JDK 21引入了分代ZGC,为Java应用程序的内存管理带来了革命性的进步。分代ZGC通过将堆内存划分为年轻代和老年代,采用并发处理和染色指针技术,实现了高吞吐量、低延迟和更好的可扩展性。这一特性显著提升了系统的性能和稳定性。
127 51
|
2月前
|
NoSQL 测试技术
内存程序崩溃
【10月更文挑战第13天】
141 62
|
24天前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
2月前
|
存储 安全 Java
jdk21的外部函数和内存API(MemorySegment)(官方翻译)
本文介绍了JDK 21中引入的外部函数和内存API(MemorySegment),这些API使得Java程序能够更安全、高效地与JVM外部的代码和数据进行互操作,包括调用外部函数、访问外部内存,以及使用不同的Arena竞技场来分配和管理MemorySegment。
53 1
jdk21的外部函数和内存API(MemorySegment)(官方翻译)
|
2月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
124 21
|
3月前
|
C语言 Android开发 C++
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
本文介绍了使用MTuner软件进行Qt MinGW编译程序的内存泄漏检测的方法,提供了MTuner的下载链接和测试代码示例,并通过将Debug程序拖入MTuner来定位内存泄漏问题。
基于MTuner软件进行qt的mingw编译程序的内存泄漏检测
|
2月前
|
监控 数据可视化 Java
如何使用JDK自带的监控工具JConsole来监控线程池的内存使用情况?
如何使用JDK自带的监控工具JConsole来监控线程池的内存使用情况?
|
3月前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
71 11
|
3月前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
105 11