你好呀,我是歪歪。
上周我不是发了《我怀疑这是IDEA的BUG,但是我翻遍全网没找到证据!》这篇文章吗。
主要描述了在 IDEA 里面反编译后的 class 文件中有这样的代码片段:
很明显,这玩意就是一个语法错误。
但是当我用其他的编译器打开之后,显示又是正常的。于是我当时就断然的下了一个结论:这是 IDEA 的 BUG。
其实写那篇文章的时候我就在想,这应该是 IDEA 使用的反编译器的 BUG,但是 IDEA 使用的是什么反编译器,工作原理是什么,这一块东西就触及到我知识的盲区了。
我实在是追不下去了。
反正我是在 IDEA 里面才能复现这个 BUG,那就先把锅甩给 IDEA 吧。
但是文章发布之后,有这样的一个评论:
我一看到这个评论,我就知道,排查问题的新方向来了。
FernFlower直接冲向 github 对应的仓库:
https://github.com/fesh0r/fernflower
在网上搜到的都是这个上面这个仓库,但是你注意看这个仓库的右边的说明:
第一个单词 unofficial,就是非官方的意思。
也就是说这个仓库是“好事之人”为了更好的研究 IDEA 的反编译器,搞出来的一个镜像仓库。
虽然说的是非官方,但是项目维护人员其实都是同一批人。所以这个镜像和官方仓库中的是一模一样的,就直接看这个仓库了。
首先进入这个仓库,我就习惯性的想往 issue 里面钻,但是我发现它没有 issue。
后来才发现它的 README.md 文件第一段就写了:
请将您的错误报告和改进建议发送到这个链接:
https://youtrack.jetbrains.com/issues/IDEA
这个链接点过去,就是 jetbrains 产品的 issues 区域,
众所周知,jetbrains 旗下的产品特别的多,所以我可以只选择 IDEA 相关的:
那么我应该搜索什么关键字呢?
我也不知道,于是我试着搜了一下这个,你别说还真有意外收获:
早在 2 年前,就有人提出了这个问题:
而且除了我们说的类型不匹配的错误外,他还提出了另外一个值得优化的地方:
你看这个 result 是不是被声明了两次,是不是应该被优化一下?
我其实当时也发现这个问题了,但是本着又不是不能用的精神,也就没去深究。没想到歪打正着,在这里又遇到了。那就顺便看看到底啥情况。
所以我顺着这里的这个链接:
找到了这个 pr:
https://github.com/JetBrains/intellij-community/pull/1538
然后我看了一下这次 pr 对应的代码提交。
看了一眼我就直接关闭了。
因为我发现这玩意完全就是超了个大纲,根本看不懂:
既然代码看不懂个,换个思路,反正他说解决了,那么我就看看是哪个版本解决的不就行了吗?
回去一看发现是 2022.1 版本解决的,而我现在的版本是 2021.3:
刚好,那就趁着这个验证的机会升级一波 IDEA。
果然,我升级到 2022.1 版本之后,再次查看 class 文件,变成了这样:
这个算是意外收获,也侧面证明了这类问题确实应该是从 IDEA 的反编译器的角度下手。
我们现在回到类型不匹配的这个错误中来:
官方在前面的链接中,并没有说明这个问题解决了,但是又给我指了一条路。
我顺着这条路,来到了这个链接:
https://youtrack.jetbrains.com/issue/IDEA-203794/Decompiling-bug
从上面的测试用例中可以看出来,当 int 的值比较大的时候,即使它没有被使用,也不会被优化为 true。
那么这个“比较大”的边界是什么呢,或者说分界点是什么呢?
我也不知道,于是我采取了二分策略,找到了边界值:
32767,就是这个边界值。
那么这个值难道有什么特殊含义吗?
我也不知道,于是搜了一下,才发现原来是 short 的边界值:
好吧,short 这玩意着实用的不多,不眼熟也很正常的嘛。
但是它和编译器把这个范围内的、未使用的 int 类型的值变为 true 之间有什么关系呢?
这个我真不知道,因为这个 BUG 官方也没有给解决,也没有给答复。
这个问题最早可以追溯到 2018 年:
但是这个 BUG 下面并没有任何人给任何回复。
时隔四年,在这个问题下是一个程序员和另外一个程序员的一次擦肩而过和无功而返。
虽然我还是没找到具体的触发 BUG 的代码,但是目前足以说明, 下面这个图片中的问题并不是 IDEA 的 BUG,而是 IDEA 的反编译器插件 Fernflower 的 BUG:
另外,其实我做了一次挣扎,我在 Fernflower 的 README.md 文件中看到了这一句描述:
将 int 1 解释为 boolean true,目的是为了解决编译器的 BUG。
我并不知道这个和我的问题有没有相关度,但是我还是试着去找了一下对应代码提交的地方。
我开始想的谁提交的相关的代码,可能在提交的 commit 信息里面会有蛛丝马迹。
但是没有。
我找到了代码对应的地方:
但是这行代码从 2014 年第一次提交的时候就是存在的。也就是说作者在最开始的时候就考虑到了 0 和 true 之间的关系。
只不过我在初始化提交的文件头中看到了一个网站,我以为这里面会有一些线索什么的:
于是我就那么随手一点。好家伙,我是万万没想到,变成不可描述的赌博网站了。
你也不用专门去找了,没啥看的,特别简陋的网站,一眼就是钓鱼网站。
好了,看到这里我想问你一个问题:你知道了这些有什么卵用呢?
是的,没有任何卵用。
那么恭喜你...
真没什么用我也不知道这是我写的第几个没有什么用处的知识点了。反正我记得应该不少了吧?
于是我决定要不盘点一下吧?
第一个第一次出现用不上的知识点是在《面试时遇到『看门狗』脖子上挂着『时间轮』,我就问你怕不怕?》这篇文章中:
大概就是说用 Redisson 加锁的时候,之前的 Lua 脚本中用的是 hset 之后用的是 hincrby:
这是我两年多前写的文章了,那个时候经验还不够丰富。
比如现在让我再次回到这个地方,我一看就觉得有故事:为什么平白无故的会把这个地方从 hset 修改为 hincrby 呢?
巧了,刚好我手上有 Redisson 的源码,打开看一眼:
果然是因为有 BUG 才从 hset 修改为 hincrby 的。
找到对应链接,浅看一眼:
https://github.com/redisson/redisson/issues/2551
从标题上看,是说解锁的时候抛出异常说 value 不是 integer 类型。
但是从他给的测试用例来说,和 integer 没有任何关系。
这个 issues 里面有很长的关于这个问题的讨论,有兴趣可以自己去看一下。
我只贴个结论,就是把 lock 的脚本从 hset 修改为 hincrby:
还是怪自己太年轻,错过了一个素材,没有看到事物的本质。
第二个第二个是出现在《这个Map你肯定不知道,毕竟存在感确实太低了。》这篇文章。
全片就是介绍了 IdentityHashMap 这个玩意,IdentityHashMap 的核心点在于 System.identityHashCode 方法。
然后我又画了很多图来说明 identityHashMap 的存储套路: key 的下一个位置,就是这个 key 的 value 值。它的数据结构不是数组加链表,就完完全全是一个数组。
但是说真的,我在工作中还真没有用过这个玩意。都是 HashMap 直接一把梭。
所以,目前为止,真没什么用。
第三个第三个是出现在《这个Bug的排查之路,真的太有趣了。》这篇文章。
是从《深入理解Java虚拟机》书里面的一段代码讲起,引出了一个叫做 Monitor Ctrl-Break 的线程。
这个线程是 idea 特有的线程,通过 javaagent 的方式运行起来,对于我们来说完全无感知。
但是当 idea 直接 Run 起来的时候,它又会被作为活跃线程,所以下面的代码就是有问题的。
因为除了主线程之外,还有一个 Monitor Ctrl-Break 线程:
//等待所有累加线程都结束
while(Thread.activeCount()>1)
Thread.yield();
当时我还费了老大的劲儿搞了这个动图,不知道还有没有人记得:
确实是一个非常冷的知识点,没啥用处,但是还记得我探索这个问题的时候,从一头雾水到逐渐清晰的过程,是一个很美妙的感受。
没有查阅任何资料,就是通过自己平时的小积累和大胆的猜测最终找到了问题的答案。
虽然好像对于实际工作中的帮助不大,所以我把它归属于没有什么卵用的知识点。
但是,这种偏门的事情,你知道了之后,以后万一用上了,那一定是一个非常装逼的场面。
第四个第四个是出现在《真是绝了!这段被JVM动了手脚的代码!》这篇文章。
这是一个关于安全点机制带来的长时间睡眠问题。
看到这个案例的时候我想到了之前在书上也看到过相关的例子,当时想的是:这玩意学它干啥啊,我写业务代码而已,肯定是遇不到这样的问题呀。
实际情况是,我确实没有遇到。但是我也确实看到了,我们的监控机制中,有专门针对 safepoint 相关的监控,以及我看到的很多问题排查文章,都会提到这个排查的方向。
以前觉得没啥用的知识点,以后说不定就用上了。
第五个第五个是出现在《414天前,我以为这是编程玄学...》这篇文章。
主要是关于使用 volatile ,又使用了输出语句的时候,导致实验结论和预期的不符合,从而引出的各种奇奇怪怪的问题。
比如这个文章中说到的 final 关键字在其中起到的作用。
额,这个知识点吧,确实用处不大。
但是,我在各个技术群里面经常看到大家验证 volatile 特效的时候会踩到加入输出语句的坑。
我都会先问一句:是不是初学者?
如果是的话就别折腾了,也别深究了,这玩意,水很深,你把握不住。
第六个第六个是出现在《就这?一个没啥卵用的知识点。》这篇文章。
额,这个你看题目就知道...
文章很短,主要是回答了下面这个问题:
结论就是:一回事。
第七个第七个是出现在《我承认,看过亿点点。》这篇文章。
关于 hashCode 的值和对象内存地址之间的关系,据说这是一道面试题,我去翻了一下源码。
hashCode 的值和对象内存地址之间的关系就是:没有关系。
第八个第八个是出现在《这题答案不在源码里...》这篇文章。
这篇文章是在回答读者的一个问题: Java 的异常是如何抛出来的?
答案藏在字节码中,所以随着这条路找到了 jvm 规范。
而在规范中意外发现一个方法的代码长度,从字节码的层面来说是有限制的。
冷知识,用处不大。
第九个第九个是出现在《报告,书里有个BUG!》这篇文章。
就是看《深入理解 JVM 虚拟机》(第三版)这本书的时候发现其中一段描述有点意思:
于是我去挖了一下。
额...
当时挖的津津有味,但是现在其实我已经忘记了文章里面写的是什么了。
仔细看了一遍,发现,嗯,确实没有骗你,确实是用处不大的知识点
第十个第十个是出现在《几行烂代码,我赔了16万。》这篇文章。
在并发加事务的双重 buff 下,修改事务的隔离级别为串行化。
不是卧龙凤雏,想不出这么绝的方案。
确实属于没有用的范围。
第十一个第十一个是出现在《填个坑!再谈线程池动态调整那点事。》这篇文章。
线程池动态调整的时候需要一个自定义队列。
而这个队列,一般来说就是把 LinkedBlockingQueue 简单的修改一下,但是 put 方法也需要进行对应的修改。
但但是线程池里面并没有用队列的 put 方法,而是用的 offer 方法。
这是我之前写的时候完全没有考虑的到的问题,是和一个大佬探讨的时候,大佬给我分享的。
如果只是把场景限定为线程池使用,那么不会有什么问题。但是既然都自定义了,就应该把各个方法都维护好。
用处不是特别大,毕竟用动态调整的方案的人并不多,主要是拿捏一波细节。
第十二个第十二个是出现在《我被这个浏览了 746000 次的问题惊住了!》这篇文章。
反正以后只要碰到时间不对劲的问题,不管它是有多奇葩,多不可思议,想都不用想,十有八九都是时区问题。
朝这个方向找方案就对了。
第十三个第十三个是出现在《算是我看源码时的一个小技巧吧~》这篇文章。
这篇文章里面有一段描述我写了很长,在那段描述里面,你知道了看门狗的生日是 7 月 4 日,也知道了它有好几个曾用名...
盘点的差不多就是这些了,我记得应该是还有的,但是我实在是想不起来了。
想起来了又怎么样呢?
是的,想起来了也没有什么用。