同学们好,今天二哥是来还债的,记得先拖到文末点个赞再回来细细的读,好不好!
最近一段时间,我一直在学习 Java 虚拟机和字节码方面的知识,为的就是有朝一日成为真正牛逼的技术大佬!不知道大家有没有这种感觉,就是一开始学习编程的时候,真心不想看底层的东西,就想直接上来撸代码,但时间久了以后,总感觉缺点啥~~~~
于是我开始阅读《深入理解计算机系统》、《图解 TCP/IP》、《深入理解 Java 虚拟机》这些偏底层的书籍,看得烦了,就去刷我之前给大家推荐过的两个视频课,《哈佛大学的 CS50》和《计算机科学速成课》,慢慢的,就有一种顿悟的感觉,嗯,这种感觉还是挺舒服的,很容易飘的那种(嘿嘿)。
我之前已经分享过三篇关于 Java 虚拟机和字节码方面的内容,大家可以再温习一遍。
class 文件
JVM 内存区域
Java 虚拟机栈
这三篇的内容还是非常肝的,读起来也比较轻松,但如果你是初学者,读起来感觉很吃力的话,不要紧,我再来补一篇更全面、更细致、更通俗的,从另外一个视角切入,完事了可以把这四篇一起添加到收藏夹,以后兴致比较高的时候可以再咀嚼下。
01、字节码
计算机比较“傻”,只认 0 和 1,这意味着我们编写的代码最终都要编译成机器码才能被计算机执行。Java 在诞生之初就提出了一个非常著名的宣传口号: “一次编写,处处运行”。
Write Once, Run Anywhere.
为了这个口号,Java 的亲妈 Sun 公司以及其他虚拟机提供商发布了许多可以在不同平台上运行的 Java 虚拟机,而这些虚拟机都拥有一个共同的功能,那就是可以载入和执行同一种与平台无关的字节码(Byte Code)。
有了 Java 虚拟机的帮助,我们编写的 Java 源代码不必再根据不同平台编译成对应的机器码了,只需要生成一份字节码,然后再将字节码文件交由运行在不同平台上的 Java 虚拟机读取后执行就可以了。
如今的 Java 虚拟机非常强大,不仅支持 Java 语言,还支持很多其他的编程语言,比如说 Groovy、Scala、Koltin 等等。
来看一段代码吧。
public class Main { private int age = 18; public int getAge() { return age; } }
编译生成 Main.class 文件后,可以在命令行使用 xxd Main.class 打开 class 文件(我用的是 Intellij IDEA,在 macOS 环境下)。
对于这些 16 进制内容,除了开头的 cafe babe,剩下的内容大致可以翻译成:啥玩意啊这…
同学们别慌,就从"cafe babe"说起吧,这 4 个字节称之为魔数,也就是说,只有以"cafe babe"开头的 class 文件才能被 Java 虚拟机接受,这 4 个字节就是字节码文件的身份标识。
目光右移,0000 是 Java 的次版本号,0037 转化为十进制是 55,是主版本号,Java 的版本号从 45 开始,每升一个大版本,版本号加 1,大家可以启动福尔摩斯模式,推理一下。
再往后面就是字符串常量池。《class 文件》那一篇我是顺着十六进制内容往下分析的,可能初学者看起来比较头大,这次我们换一种更容易懂的方式。
02、反编译字节码文件
Java 内置了一个反编译命令 javap,可以通过 javap -help 了解 javap 的基本用法。
OK,我们输入命令 javap -v -p Main.class 来查看一下输出的内容。
Classfile /Users/maweiqing/Documents/GitHub/TechSisterLearnJava/codes/TechSister/target/classes/com/itwanger/jvm/Main.class Last modified 2021年4月15日; size 385 bytes SHA-256 checksum 6688843e4f70ae8d83040dc7c8e2dd3694bf10ba7c518a6ea9b88b318a8967c6 Compiled from "Main.java" public class com.itwanger.jvm.Main minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #3 // com/itwanger/jvm/Main super_class: #4 // java/lang/Object interfaces: 0, fields: 1, methods: 2, attributes: 1 Constant pool: #1 = Methodref #4.#18 // java/lang/Object."<init>":()V #2 = Fieldref #3.#19 // com/itwanger/jvm/Main.age:I #3 = Class #20 // com/itwanger/jvm/Main #4 = Class #21 // java/lang/Object #5 = Utf8 age #6 = Utf8 I #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/itwanger/jvm/Main; #14 = Utf8 getAge #15 = Utf8 ()I #16 = Utf8 SourceFile #17 = Utf8 Main.java #18 = NameAndType #7:#8 // "<init>":()V #19 = NameAndType #5:#6 // age:I #20 = Utf8 com/itwanger/jvm/Main #21 = Utf8 java/lang/Object { private int age; descriptor: I flags: (0x0002) ACC_PRIVATE public com.itwanger.jvm.Main(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: bipush 18 7: putfield #2 // Field age:I 10: return LineNumberTable: line 6: 0 line 7: 4 LocalVariableTable: Start Length Slot Name Signature 0 11 0 this Lcom/itwanger/jvm/Main; public int getAge(); descriptor: ()I flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #2 // Field age:I 4: ireturn LineNumberTable: line 9: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/itwanger/jvm/Main; } SourceFile: "Main.java"