前言
随着Java开发技术不断被推到新的高度,因此越来越需要具备对更深入的基础技术的理解,比如Java字节码相关。JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它其实可以理解为是一个虚构出来的计算机,是通过实际的计算机仿真模拟各种计算机功能来实现的。引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译,就可以在多种不同的平台上运行。而运行的就是本文要说的字节码。
字节码的探索
什么是Java字节码指令?简而言之,Java字节码指令就是Java虚拟机能够听得懂和可执行的指令,可以说是Java代码的最小执行单元,有点java基础的人一定都知道javac命令会将java源文件编译成字节码文件,其中就包含了大量的字节码指令。接下来用每个语言都有的灵魂例子(HelloWorld)做下说明,如下:
■java代码
public class HelloWorld { public static void main(String[] args) { System.out.println("HelloWorld"); }
}
■字节码
... ...省略 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String HelloWorld 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 3: 0 line 4: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String;}SourceFile: "HelloWorld.java"
可以看到短短几行java代码生成的字节码却比较的长,因篇幅有限,仅针对关键代码进行说明,以上stack指的是操作数栈,包含的就是字节码指令。LineNumberTable指的是描述java源代码行号与字节码行号之间的对应关系,LocalVariableTable指的是局部变量表中的变量和java源码的变量之间的关系。接下来介绍一下执行顺序,java源码相对比较简单,意思就是把HelloWorld这个英文单词打印到控制台显示出来。而字节码却是按部就班分成了4个指令依次执行,为便于理解,举一个射击子弹的例子结合以上的代码逐一对比说明:
1:获取一把手枪↓
// 加载打印对象入栈0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
2:加载子弹入弹夹↓
// 加载常量池中的字符串入栈3: ldc #3 // String HelloWorld
3:执行击发动作↓
// 执行打印动作出栈5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
4:放下枪,动作结束↓
// 返回栈顶结束8: return
经过上面这个例子,相信大家对整个字节码指令的执行,应该有了一个大概的认识,因为以上的例子相对比较简单,因此涉及到的字节码指令也只有以上4个,但是即使如此,也还是用到了加载指令和方法调用和返回指令,都是出现频率非常高的几大类字节码指令了。
字节码的思考
可能大家觉得相较于Java代码而言,字节码指令显得有点晦涩难懂,或者不太明白学习字节码指令的意义在哪里,人总是根据自己已经掌握的知识和技能来解决问题的,这里有个悖论,有时候你觉得有些技术没用恰恰是因为你没有熟练掌握它,遇到可以使用它的场景你根本想不到用。字节码指令可以从比源码更深的层面去学习 Java 相关知识虽然不可能所有问题都用字节码的知识来解决,但是它给你一个学习的途径。比如通过字节码的学习你可以更好地理解 Java中各种语法和语法糖背后的原理,更好地理解多态等语言特性。
最后附上字节码的脑图↓
结语
纵观技术发展,新的技术层出不穷,结构也越来越复杂,但是底层的结构确是不怎么有较大的调整的,比如字节码指令,如有好的基础,对较快理解和掌握新技术是非常有帮助的,鲸技术们一直在路上不断努力和追求着。