中国会议营销网站,做视频网站怎么看不会卡,新竹自助建站系统,成都微信网站建设推来源 | why技术荒腔走板大家好#xff0c;我是 why#xff0c;老规矩#xff0c;先来一个简短的荒腔走板#xff0c;给冰冷的技术文注入一丝色彩。上面图片中这个正在奔跑的少年#xff0c;是正在参加校运会的我#xff0c;那一年我 18 岁#xff0c;高三。参加的项目是… 来源 | why技术荒腔走板大家好我是 why老规矩先来一个简短的荒腔走板给冰冷的技术文注入一丝色彩。上面图片中这个正在奔跑的少年是正在参加校运会的我那一年我 18 岁高三。参加的项目是 3000 米长跑那个时候长跑一向是大家都不喜欢的运动但是我喜欢。我是从小乡村出来的小学、初中的时候操场就是用煤渣加泥土铺成的一圈仅 200 米的跑道。在那个 200 米的跑道上我从小学跑到初中跑了不知道多少公里。初中的时候参加了学校长跑队的集训。有一天集训完成之后体育老师给我们说你们一定要刻苦训练将来要到城里面去读书。你们知道吗城里面的跑道一圈是 400 米还是用塑胶铺成的跑道跑上去不知道比这个煤渣舒服多少倍。我知道长跑很痛苦但是如果你真的喜欢它你就得享受这个痛苦的过程。不要放弃特别是在最后冲刺阶段。因为如果你不拼尽全力等你到终点之后你总是会觉得自己能再快一点可惜比赛已经结束了。后来我退出了集训队但是老师的话我一直记得。我也如愿进入了城里读高中见到了传说中的“塑胶跑道”。还参加了开在塑胶跑道上的运动会。前段时间我翻到这张照片的时候我就想起了初中集训的一些点滴记忆也想起了老师的话享受痛苦咬牙坚持无愧结果。这周今年的高考也结束了。其实这个道理放在高考和高考之后的人生会面临的更多更复杂的挑战面前都可以。人生嘛无非就是奔跑跌倒奔跑。奔跑吧骄傲的少年。前面有更多的机会和更难的挑战在等着你。好了说回文章。从一个BUG说起前段时间翻到了一个 JDK 有点意思的 BUG带大家一起瞅瞅。https://bugs.java.com/bugdatabase/view_bug.do?bug_id8137185memory leak内存泄漏。是谁导致的内存泄漏呢ConcurrentLinkedQueue这个队列。这个 BUG 里面说在 jetty 项目里面也爆出了这个 BUG我看了一下觉得 jetty 的这个写的挺有意思的。我按照 jetty 的这个讲吧反正都是同一个 JDK BUG 导致的。地址如下https://bugs.eclipse.org/bugs/show_bug.cgi?id477817我用我八级半的蹩脚英语给大家翻译一下这个叫做 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/?originalSubdomainaujetty 项目的领导者短短的几个单词就足以让你直呼牛逼。高端的食材往往只需要最简单的烹饪。高端的人才往往只需要寥寥数语的介绍。大佬的简历就是这么朴实无华且枯燥。而且你看这个头像。哎酸了酸了。果然再次印证了这句话变秃了也变强了并不适用于外国的神仙。好了我们看一下这个 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 分钟的时间。然后在凌晨 2 点 57 分这个时间点大佬都是不用睡觉的吗还是说刚修完福报下班了 max 回复到我不敢相信 CLQ 使用起来会有这样的问题他们至少应该在 API 文档里面说明一下。这里的他们应该指的是 JDK 团队的成员特指 Doug Lea毕竟是他老爷子的作品。为什么没有在 API 文档里面说明呢因为他们自己也不知道有这个 BUG 啊。Greg 连着回复了两条并且直接指出了解决方案问题的原因是 remove 方法的源码里面有上图中标号为 ① 的这样一行代码。这行代码会去取消被移除的这个 node 其值已经被替换为 null和 list 之间的链接然后可以让 GC 回收这个 node。但是当集合里面只有一个元素的时候 next ! null 这个判断是不成立的。所以就会出现这个需要移除的节点已经被置为 null 了但却没有取消和队列之间的连接导致 GC 线程不会回收这个节点。他给出的解决方案也很简单就是标号为②、③的地方。总之只需要让代码执行 pred.casNext 方法就行。总之一句话导致内存泄漏的原因是一个被置为 null 的 node由于代码问题导致该 node 节点既不会被使用也不会被 GC 回收掉。如果你还没理解到这个 BUG 的原因说明你对 CLQ 这个队列的结构还不太清晰。那么我建议你读一下《Java并发编程的艺术》这一本书里面有一小节专门讲这个队列的图文并茂写的还是非常清晰。这个 BUG 在 jetty 里面的来龙去脉算是说清楚了。然后我们再回到 JDK BUG 的这个链接中去他这里写的原因就是我前面说的原因没有 unlink所以不能被回收。而且他说到这个 BUG 在最新的JDK 7、8和9版本中都存在。他说的最新是指截止这个 BUG 被提出来之前Demo跑起来这一小节里面我们跑一下 Greg 给的那个修复 Demo亲手去摸一下这个 BUG 的样子。https://bugs.eclipse.org/bugs/attachment.cgi?id256704你可以打开上面那个链接直接复制粘贴到你的 IDEA 里面去注意第 13 行因为 Greg 给的是修复 Demo所以用的是 ConcurrentHashSet由于我们要演示这个bug所以使用 CLQ。这个 Demo 就是在死循环里面调用 queue 的 add(obj) 和 remove(obj) 方法。每循环 10000 次就打印出时间间隔、队列大小、最大内存、剩余内存、总内存的值。最终运行起来的效果是这样的JDK 版本是 1.7.0_71可以看到每次打印 duration 这个时间间隔是越来越大队列大小始终为 1。后面三个内存相关的参数可以先不关心下一小节我们用图形化工具来看。你知道上面这个程序到我写文章写到这里的时候我跑了多久了吗61 小时 32 分 53 秒。最新一次循环 10000 次所需要的时间间隔是 575615ms快接近 10 分钟这就是 Greg 说的不仅仅是内存泄漏而且越来越慢。但是同样的程序我用 JDK 1.8.0_212 版本跑的时候情况却是这样的时间间隔很稳定不会随着时间的推移而增加。说明这个版本是修复了这个 BUG 的我带大家看看源码JDK 1.8.0_212 版本的源码里面在 CLQ 的 remove(obj) 方法的 502 行末尾注释了一个 unlink。官方的修复方法可以看这里http://hg.openjdk.java.net/jdk8u/jdk8u-dev/jdk/rev/8efe549f3c87改动比较多但是原理还是和之前分析的一样我仅仅在两个 JDK 版本中跑过示例代码。在 JDK 1.8.0_212 没有发现内存泄漏的问题我看了对应的 remove(obj) 方法的源码确实是修复了。在 JDK 1.7.0_71 中可以看到内存泄漏的问题。unlink一个简简单单的词背后原来藏了这么多故事。jconsole、VisualVM、jmc既然都说到内存泄漏了那必须得介绍几个可视化的故障排除工具。前面说了这个程序跑了 61 个小时了给大家看一下这个时间段里面堆内存的使用情况可以看到整个堆内存的使用量是一个明显的、缓慢的上升趋势。上面这个图就是来自 jconsole。结合程序通过图片我们可以分析出这种情况一定是内存泄漏了这是一个非常经典的内存泄漏的走势。接下来我们再看一下 jmc 的监控情况上面展示的是已经使用的堆内存的大小走势和 jconsole 的走势一样。然后再看看 VisualVM 的图VisualVM 的图我不知道怎么看整个运行了 60 多小时的走势图但是从上面的图也是能看出是有上升趋势的。在 VisualVM 里面我们可以直接 Dump 堆然后进行分析可以清楚的看到 CLQ 的 Node 的大小占据了 94.2%。但是从我们的程序来看我们根本就没有用到这么多 Node。我们只是用了一个而已。你说这不是内存泄漏是什么。内存泄漏最终会导致 OOM。所以当发生 OOM 的时候我们需要分析是不是有内存泄漏。也就是看内存里面的对象到底应不应该存活如果都应该存活那就不是内存泄漏是内存不足了。需要检查一下 JVM 的参数配置-Xmx/-Xms根据机器内存情况判断是否还能再调大一点。同时也需要检查一下代码是否存在生命周期过程的对象是否有数据结构使用不合理的地方尽量减少程序运行期的内存消耗。我们可以通过把堆内存设置的小一点来模拟一下内存泄漏导致的 OOM。还是用之前的测试案例但是我们指定 -Xmx 为 20m即最大可用的堆大小为 20m。然后把代码跑起来同时通过 VisualVM 、jconsole、jmc 这三个工具监控起来为了我们有足够的时候准备好检测工具我在第 8 行加入休眠代码其他的代码和之前的一样加入 -Xmx20m 参数运行起来之后我们同时通过工具来查看内存变化下面三个图从上到下的工具分别是 VisualVM、jconsole、jmc从图片的走势来看和我们之前分析的是一样的内存一直在增长。程序运行 19 分 06 秒后发生 OOM 异常那正常的走势图应该是怎么样的呢我们在 JDK 1.8.0_121 版本中已经修复了 remove 方法用相同的 JVM 参数-Xmx20m再跑一下首先从上面的日志中可以看出时间间隔并没有递增程序运行的非常的快。然后用 VisualVM 检测内存同样跑 19 分钟后截图如下可以看到堆内存的使用量并没有随着时间的推移而越来越高。但是还是有非常频繁的 GC 操作。这个不难理解因为 CLQ 的数据结构用的是链表。而链表又是由不同的 node 节点组成。由于调用 remove 方法后node 节点具备被回收的条件所以频繁的调用 remove 方法对节点进行删除会触发 JVM 的 min GC。这种 JDK BUG 导致的内存泄漏其实挺让人崩溃的。首先你第一次感知到它是因为程序发生了 OOM。也许你会先无脑的加大堆内存空间恰好你的程序运行了一周之后又要上线了所以涉及到重启应用。然后很长一段时间内没有发生 OOM 了。你就想这个问题可能解决了。但是它还是在继续发生着很可能由于节假日前后不能上线比如国庆七天加上前后几天大概有半个月的样子应用没有上线所以没有重启程序越来越慢最终导致第二次 OOM 的出现。这个时候你觉得可能不是内存溢出这么简单了。会不会是内存泄漏了然后你再次重启。这次重启之后你开始时不时的 Dump 一下内存拿出来分析分析。突然发现这个 node 怎么这么多呢最终找到这个问题的原因。原来是 JDK 的 BUG。你就会发出和 Greg 一样的感叹卧槽震惊这么牛皮我这个运行了 60 多小时的程序到现在堆内存使用了 233m但是我整个堆的大小是接近 2G。通过 jmc 同时展示堆的整体大小和已经使用的堆大小你可以发现距离内存泄漏可以说是道阻且长了我粗略的算了一下这个程序大概还得运行 475 个小时左右也就是 19 天之后才会出现由于内存泄漏导致的 OOM。我会尽量跑下去但是听到我电脑嗡嗡嗡的风扇声我不知道它还能不能顶得住。如果它顶住了我在后面的文章里面通知大家。好了图形化工具这一小节就到这里了。我们只是展示了它们非常小的一个功能合理的使用它们常常能达到事半功倍的作用。如果你不太了解它们的功能建议你看看《深入理解JVM虚拟机第3版》里面有一章节专门讲这几个工具的。最后说一句这是我昨天晚上写文章的时候拍的 女朋友说一眼望去感觉我是一个盯盘的人在看股票走势图这只股票太牛逼了。要是股市的总体走势也像内存泄露那么单纯而直接就好了。只要在 OOM 之前落袋为安就行。可惜有的人就是在 OOM 的前一刻满仓杀入真是个悲伤的故事。推荐阅读仅用2年过渡到自研ARM芯片苹果的底气从何而来开源巨头 SUSE 收购 Rancher Labs云原生时代来临国内厂商 Onyx 违反 GPL 协议中国开源何去何从天上地下马斯克和贝佐斯终有一战空间-角度信息交互用于光场图像超分辨重构性能达到最新SOTA | ECCV 2020赠书 | DeFi沉思录历史、中国与未来点分享点点赞点在看