两个java命令和一道看起来比较简单的面试题

32次阅读

共计 2426 个字符,预计需要花费 7 分钟才能阅读完成。

本来这篇文章是在我的另一篇算法文章之后才发布的, 但是那篇文章一直卡在一个点上, 卡了两个星期, 按照我的规划来说, 一周是要写两篇, 所以只好先完成这一篇。

(1)

我们知道在 java 中用 == 进行比较的时候,比较的是内存中的地址. 对于非 new 方式产生的字符串, 在字符串常量池中。而字符串常量池位于方法区(也称永久代), 在 jdk1.7 以下是跟堆隔离的。非 new 方式产生的字符串在进入字符串常量池之前会检查字符串常量池中是否已经存在该字符串了, 如果有就不再重复的产生。比如你你用非 new 方式产生两个 ”helloworld”, 用 a 和 b 分别指向他们, 用 = = 去比较, 那么结果是 true。

那请问图 (1) 怎么解释呢? e 不是也是非 new 方式产生的吗? 那么比较结果为什么还是 false? 答案就是 e 是 new 方式产生的。这个时候可能有同学会说, 你是不是以为我瞎啊! 明明就是非 new 方式产生的好吗? 那么请先不要着急, 请听我细细道来。

很多时候, 我们 java 程序员在优化自己的代码, 其实编译器也在优化你的代码, 你认为 d + “world” 的时候虚拟机在调用的是哪个方法进行拼接呢?我们知道 java 源代码由编译器变成字节码,在由源代码到字节码这个过程中,编译器做了大量的工作,相信各位在大学学过的编译原理的都知道。我们可以通过 JVM 提供的一些指令, 来看清 ” 字节码 ”, 就是虚拟机在执行这一行的时候,做了什么?

javap The Java Class File Disassembler
-c You use the javap command to disassemble one or more class files
-verbise: Prints stack size, number of locals and args for methods.

javap -c 就是反汇编字节码
javap -verbose 比 - c 更进一步打印出来栈打大小和方法参数

关于这个指令来说, 有的人将其称为反编译, 有的人称其为反汇编。
我们首先来解释反编译和反汇编的不同, 在接着来说哪一个更译名更为合适。
反编译: 高级语言源程序经过编译变成可执行文件,反编译就是逆过程。
反汇编: 反汇编(Disassembly):把目标代码转为汇编代码的过程,也可以说是把机器语言转换为汇编语言代码
disassemble 和 disassembly, 从这个角度上来讲, 称呼这个指令为反编译更为贴切些。

那么 java 字节码可以称之为机器语言喽? 那什么叫机器语言呢?能被计算机直接识别的编程语言吗?这个定义还是很粗糙。

机器语言(machine language)是一种指令集的体系。这种指令集称为机器代码(machine code),是计算机的 CPU 可直接解读的数据。机器代码有时也被称为原生码(Native Code),这个名词比较强调某种编程语言或库与运行平台相关的部分。
机器语言是用二进制代码表示的、计算机能直接识别和执行的一种机器指令的集合。它是计算机的设计者通过计算机的硬件结构赋予计算机的操作功能。机器语言具有灵活、直接执行和速度快等特点。不同种类的计算机其机器语言是不兼容的,按某种计算机的机器指令编制的程序不能在另一种计算机上执行 — 维基百科

那什么叫指令集呢?

指令集架构(英语:Instruction Set Architecture,缩写为 ISA),又称指令集或指令集体系,是计算机体系结构中与程序设计有关的部分,包含了基本数据类型,指令集,寄存器,寻址模式,存储体系,中断,异常处理以及外部 I /O。指令集架构包含一系列的 opcode 即操作码(机器语言),以及由特定处理器执行的基本命令。— 维基百科

那什么叫指令?

在计算机技术中,指令是由指令集架构定义的单个的 CPU 操作。在更广泛的意义上,“指令”可以是任何可执行程序的元素的表述,例如字节码。

讨论到这里, 就有些收不住了。关于字节码和机器语言, 有时间的话会专门写一篇文章.

我们姑且就称 javap 为反汇编指令, 它可以将字节码变成一种类似于汇编指令, 汇编指令相对于机器语言就好懂的多了.
我们来解释图 1, 事实上, 虚拟机在执行 d +”helloworld” 的时候, 是调用了 StringBuilder 方法,然后在调用 StringBuilder 的 toString 方法返回给 e, 而 StringBuilder 的 toString()方法, 又是采用了 new 的方式

那么有什么证据呢?

相信各位大概都可以看懂了, 这是反汇编图 (1) 的字节码的产物

如果程序变成下面这样呢?

此时的运行结果为 true, 我们再次反汇编其字节码。来看看仅仅是加了一个 final 关键字, 字节码发生了什么变化?

我们发现在第六行 e 就已经变成 helloworld 了, 这个时候就没有再调用 StringBuilder, 仍然是非 new 方式产生的。
那这是为什么呢?

这就是编译器的常量折叠技术, 非 java 所独有。对于一些表达式符合语言中所定义的常量条件, 那么这些表达式在编译器就会被编译器进行计算,而不会等到运行时在进行计算。

那么 javap 还可以帮我们解糖, 在 java 中存在着相当一部分的语法糖。

语法糖: 语法糖(Syntactic sugar)是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性

举例: 注解并非是一种新的数据类型, 而是一种特殊的接口。不信的话, 我们反汇编一下注解, 就可以看明白我说的

反汇编后的结果:

那 synchronized 呢? 这个在分解为什么了呢?
对于静态方法和非静态方法还是有点小不同的, 以后会专门写一篇文章来介绍。
某些情况下, 用反汇编指令我们可以清楚的看到,for 和 foreach 是一回事
for 和 foreach 又有一些不同。

在 IDEA 中可以直接配置 javap -c 和 javap -verbose 这两个指令, 这样就不用每次就用 CMD 方式了, 方便快捷。如何配置, 网上有很清晰的教程, 这里我就不介绍了。

正文完
 0