《深入理解Java虚拟机》第2版挖的坑终于在第3版中被R大填平了 (上)

简介: 《深入理解Java虚拟机》第2版挖的坑终于在第3版中被R大填平了 (上)

这是why技术的第34篇原创文章


本周还是在家办公的一周,上面的图就是我在家的工位,和上周《Dubbo Cluster集群那点你不知道的事》这篇文章里面的第一张图片比起来,升级了显示器支撑臂,如果短还可以加长;用上了机械键盘,让指尖享受那一点点来自红轴的美妙反馈......


还是那句话:工欲善其事,必先利其器。在家办公,我是认真的。


图中显示器下面的两本书分别是《深入理解Java虚拟机》的第2版和第3版。也就是本文的主角。


你的手边有第2版吗?


来,翻到第57页。这里面有个“坑”,看看你当时发现了没,有没有在这页做笔记呢?


没有也没关系,我带你先回顾一下这一页的内容,再让你看看我三年前第一次看这书的时候做的笔记。


第2版57页讲了啥?


也许你根本就没看过《深入理解Java虚拟机(第2版)》这本书。但是你一定见过位于本书第57页的示例代码:


由于JDK 6常量池位于方法区,JDK 7以后常量池位于堆中,所以用两个版本的jdk跑上面的代码就会出现神奇的事情。甚至用JDK 8来跑,也会出现你想不到的结果。且听我慢慢道来。


先说一下intern是干啥的。


该方法的作用是把首次遇到的字符串加载到常量池中


再看一下intern的注释:


其中标记了☆的红框翻译过来就是:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。


回到最开始的代码中。引用第三版的描述如下:


这段代码在JDK 6中运行,会得到两个false,而在JDK 7中运行,会得到一个true和一个false。


产生差异的原因是:在JDK 6中,intern()方法会把首次遇到的字符串实例复制到永久代的字符串常量池中存储,返回的也是永久代里面这个字符串实例的引用,而由StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用,将返回false。


而JDK 1.7(以及部分其他虚拟机,例如JRocki)的intern()实现就不需要再拷贝字符串的实例到永久代了,既然字符串常量池已经移到了Java堆中,那只需要在常量池里记录一下首次出现的实例引用即可,因此intern()返回的引用和由StringBuilder创建的那个字符串实例就是同一个。


对str2比较返回false是**因为“java”这个字符串在执行StringBuilder.toString()之前已经出现过,**字符串常量池中已经有它的引用了,不符合intern()方法要求“首次出现”的原则,而“计算机软件”这个字符串则是首次出现的,因此返回true。


挖坑不填,坑哭读者


读到这里你有没有一些不惑呢?有没有感觉到一丝丝不对呢?


我们再看看原文:


为什么在JDK 7里面会返回fasle,上面红框框起来的部分是关键答案:


因为“java”这个字符串在执行StringBuilder.toString()之前已经出现过。


这句话就是“坑”,已经出现过?在哪出现的,你倒是告诉我啊!我当时的内心想法和下面的老大哥是一样一样的:


我第一次看这本书是在2016年,看这个地方的时候,我就百思不得其解,在哪就出现过了呀?


当时也不知道是在哪个写的似是而非的博客里面找到“java是关键字,已存在常量池中”这句“骚话”。还正正经经的抄了上去,虽然是错误的描述,虽然字是丑了点......


你当年或者现在看的时候有这个疑惑吗?


之后我又完整的看过几次这本书,我清楚的记得,我再一次看到这里的时候我就觉得**“java是关键字,已存在常量池中”这个描述是不对的**,所以我在后面打了一把叉,再次去找了相关资料,找到了sun.misc.version,终于解决了“在哪出现的”这个问题。


第3版注脚填坑


这个2013年(第二版出版那年)挖下的坑,在2016年10月1日,就被R大在知乎上给填上了。R大的这个回答也被作者周志明写在了2019年底出版的《深入理解Java虚拟机(第三版)》的注脚里面:


里面的RednaxelaFX就是R大,一个把虚拟机玩到极致,凭一己之力撑起了知乎java半边天的男人,后面我会详细介绍一下的。


你只要了解到一点就行:他的回答,就是权威。


在R大的这个知乎回答中,帮周志明大大填了这个坑,我强烈建议你一定要去看看,链接如下


https://www.zhihu.com/question/51102308/answer/124441115


R大帮忙填坑


我这里只是结合R大的回答和个人的一点点经验,谈谈自己的认知。


在2016年我读这本书的时候,我才刚刚大学毕业,刚从新手村出来。当时的我对于这个问题是绝对没有任何思路的,必须直接在网上查询答案。


现在的我,略有一点经验,再次遇到这个问题,就算没看书中的描述、R大的回答,我肯定也会想到:在书中的示例里面,第二个输出false,说明调用main方法之前,肯定在字符串常量池里面已经有了这个“java”字符串了。


怎么验证一下呢?


我们在main方法的第一行打上一个断点,debug运行程序后,可以看到Memory,然后过滤出String,如下:


然后双击过滤出来的java.lang.String,可以看到下图:


在这个页面我们可以继续过滤:


果然,在程序还没执行第14行之前,“java”已经出现了。


从这个结果我们可以推断出:Java标准库在JVM启动过程中加载的部分,可能里面就有类里有引用“java”字符串字面量,这个字面量被初次引用的时候就会被intern,加入到字符串常量池中去。


**而到底是哪个类导致了这个“java”字符串被intern的呢?**R大主要就是回答了这个问题。


我截取一下R大最终的答案,具体探索的过程去看他的回答吧,很强很硬核:

目录
相关文章
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
483 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
Java 数据安全/隐私保护 Windows
【Azure Developer】使用Java代码启动Azure VM(虚拟机)
【Azure Developer】使用Java代码启动Azure VM(虚拟机)
210 0
|
存储 Java API
【Azure Developer】通过Azure提供的Azue Java JDK 查询虚拟机的CPU使用率和内存使用率
【Azure Developer】通过Azure提供的Azue Java JDK 查询虚拟机的CPU使用率和内存使用率
367 0
|
Java
Java常见JVM虚拟机指令(47个)
Java常见JVM虚拟机指令(47个)
383 3
Java常见JVM虚拟机指令(47个)
|
监控 Oracle Java
(一)JVM成神路之初识虚拟机 - 探寻Java虚拟机的前世今生之秘
JVM(Java Virtual Machine)Java虚拟机的概念大家都不陌生,Java之所以可以做到“一次编译,到处运行”的跨平台性,其根本原因就在于JVM。JVM是建立在操作系统(OS)之上的,Java虚拟机屏蔽了开发人员与操作系统的直接接触,我们在通过Java编写程序时,只需要负责编写Java代码即可,关于具体的执行则会由JVM加载字节码后翻译成机械指令交给OS执行。
774 1
|
监控 算法 Java
深入理解Java虚拟机:内存管理与性能优化
在Java的世界里,虚拟机(JVM)是幕后的守护者,它默默地支撑着每一个字节码的运行。本文将揭开JVM的神秘面纱,探讨其内存管理机制及如何通过调优提升应用性能。从堆内存的分配到垃圾回收的策略,再到实践中的性能监控与调优技巧,我们将一同走进JVM的内部世界,学习如何让我们的Java程序跑得更快、更稳。
Java演进问题之单个虚拟机的最大线程数量一般会设置到200至400条如何解决
Java演进问题之单个虚拟机的最大线程数量一般会设置到200至400条如何解决
424 0
|
存储 监控 算法
探索Java虚拟机:深入理解JVM内存模型和垃圾回收机制
在Java的世界中,JVM是核心所在,它不仅承载着代码的运行,还管理着内存资源。本文将带你深入了解JVM的内存模型和垃圾回收机制,通过具体数据与案例分析,揭示它们对Java应用性能的影响,并探讨如何优化JVM配置以提升效率。
501 27