计算机比较“傻”,只认 0 和 1,这意味着我们编写的代码最终都要编译成机器码才能被计算机执行。Java 在诞生之初就提出了一个非常著名的宣传口号: "一次编写,处处运行"。
Write Once, Run Anywhere.
为了这个口号,Java 的亲妈 Sun 公司以及其他虚拟机提供商发布了许多可以在不同平台上运行的 Java 虚拟机,而这些虚拟机都拥有一个共同的功能,那就是可以载入和执行同一种与平台无关的字节码(Byte Code)。
(前面其实我们也讲过,但为了这篇内容的完整性,我们简单过一下,这一节我们的重点是通过 javap 这个命令来了解字节码)
有了 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 文件(前面我们已经讲过了,还不会用的同学可以回头看一眼)。
对于这些 16 进制内容,除了开头的 cafe babe,剩下的内容大致可以翻译成:啥玩意啊这......
但经过上一节类文件结构的洗礼,相信大家对这份文件的内容已经很熟悉了。
javap
Java 内置了一个反编译命令 javap,可以通过 javap -help
了解 javap 的基本用法。
当然了,执行这个命令的前提条件是你需要配置好 Java 环境变量,如果没有配置好,可以参考这篇文章。
javap 是 JDK 自带的一个命令行工具,主要用于反编译类文件(.class 文件)。我本机是 macOS,使用了 jenv 来管理的 JDK 版本,所以看到的位置如下图所示。
Windows 用户以及没有使用 jenv 的 macOS 用户可以根据这个帖子了解 jenv,真的好用。
javap 主要用于反编译 Java 类文件,即将编译后的 .class 文件转换回更易于理解的形式。虽然它不会生成原始的 Java 源代码,但它可以显示类的结构,包括构造方法、方法、字段等,帮助我们更好地理解 Java 字节码以及 Java 程序的运行机制。
前面我们已经写了一个简单的类,大家应该还记得:
public class Main {
private int age = 18;
public int getAge() {
return age;
}
}
当然了,我希望你是用 Intellij IDEA 来编写而不是记事本,这样就省去了我们主动编译的过程,可以直接在 target 目录下找到 class 文件,这些知识我们前面都已经讲过了。
OK,我们在 class 文件的同级目录下输入命令 javap -v -p Main.class
来查看一下输出的内容(-v 显示附加信息,如局部变量表、操作码等;-p 显示所有类和成员,包括私有的,不懂的同学可以回头看在看一眼 javap -help
的输出结果 😁)。
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
真诚点赞 诚不我欺
回复