JDK的BUG导致的内存溢出!反正我是没想到还能有续集。

BUG到底是怎么修复的?

有好几个同学都来问了我一些相关的问题。

比如这样的:

写之前那篇文章的时候,我的侧重点主要在 ConcurrentLinkedQueue(下文统一缩写 CLQ)存在过一个可能会导致内存泄漏的 BUG ,这个 BUG 的来龙去脉是怎样的,以及怎么通过可视化工具让我们感受到这个 BUG 的存在。其实对于 BUG 在源码里面具体是怎样体现的,以及修改之后为什么就不会内存泄漏了并没有进行详细的解读。 开始的想法是,告诉大家有这个事情,如果有兴趣的可以直接去调试分析一下。但是有的同学反映调试也看不明白啊。一个方法,在断点处一脸懵逼的进来,又一脸懵逼的出去。

image

苦思冥想没搞清楚,然后就来问我。我发现一两句话也说不太清楚,于是把 Debug 的关键截图放到文档里面配以文字说明,才勉强能说的比较清楚一点,也不知道这位同学看明白了没。但就拿这个文档来说:真的是暖男石锤了。

image

所以,文本主要是分享一个我自己调试的奇淫技巧,最后再做个 remove 方法的解读。但是如果要深刻理解 CLQ 这个十分优秀、十分有想法的基于非阻塞方法实现的线程安全的队列,大家需要去看的是 offer、poll 方法。然后一个情况一个情况的去分析,自己拿着草稿本在上面写写画画。我也妄想过通过这篇文章给你们把它讲的明明白白的,后来我发现这对我而言难度有点大。最后再说一下如果你用 IDEA 调试时,大概率会碰到的一个巨坑。好了,先把之前的这个坑给填上。修复之后的 JDK8 到底怎么就避免了内存泄漏的问题了?

自定义CLQ

我们先看一下 CLQ 的数据结构。CLQ 的 Node 里面有一个 item(放的是存储的对象),还有一个 next 节点(指向的是当前 Node 的下一个节点)。从数据结构来看,也知道这是一个单向链表了。

image

Java 程序员,就靠日志活着的。所以我想通过日志的方式直接输出链表结构,这应该是最简单的演示方式了。为了通过日志体现出数据变化的过程,我们先来一个自定义的 CLQ。方法很简单,直接把 JDK 8 的 CLQ 复制出来一份,然后修改名称就可以,我们这里的名称是 whyConcurrentLinkedQueue(下文简写为 WhyCLQ):

image

搞一个测试用例跑一下:

image

然后你会发现报错了:

image

这个错误是关于 Unsafe 操作的,在代码的第 931 行:

image

getUnsafe 方法的源码是这样的:

image

而这个方法里面就是判断当前<typo id="typo-1073" data-origin="类" ignoretag="true">类</typo>的类加载器是不是为 null:

image

这里抛出异常了,说明不是 null。也就会说当前类的加载器不是启动类加载器 BootstrapClassLoader。我们知道,rt.jar 包下的类是需要 bootstrap 类加载器加载的。诶,巧了。这个类就位于 rt.jar 包里面:

image

来,再复习一下双亲委派机制:

image

如果我们自定义了一个 CLQ ,那么这个类的类加载器是什么类加载器呢?

我们验证一下:

image

从 Debug 的截图可以看出当前类 WhyCLQ 的类加载器是 AppClassLoader。其父类加载器(parent)是 ExtClassLoader 类加载器。

不是 BootstrapClassLoader ,所以我们这里抛出了异常。在介绍怎么解决这个异常之前,先简单的说一下 Unsafe。这个类名称一听就是非常牛逼的。Unsafe,不安全。感觉像是在钓鱼执法,表面上疯狂的在那给你摆手,说:别靠近我,别使用我,我很不安全。实际上内心是这样的:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值