Java字节码文件结构剖析
今天起开启JVM的新的知识学习篇章----Java的字节码,那学习Java字节码有啥用呢?我们知道Java是跨平台的一门语言,编写一次到处运行,而支撑着这个特性的根基为两点:JVM和.class字节码文件,java在不同的平台上编译生成的.class文件是一模一样的,而JVM针对不同的操作系统会有不同的JVM,也就是JVM其实并非是跨平台的,而生成的.class文件其实是一个二进制文件,该文件并非是给人看的,而是给机器看的,所以肯定是看不懂的,但是针对字节码JVM是有规范定义的,而如今基于JVM的语言也越来越多,如:scala,groovy,kotlin等,虽说语言不同但是编译出来的字节码文件都是符合JVM规范的,也就是对于JVM来说不管咱们用的是哪种语言编写的,只关心字节码文件是否符合字节码的规范,只要符合了就可以加载运行。另外在了解了字节码文件之后,对于很多深层次的东东【如synchronized关键字、volatile关键字】就会了解得更加透彻了,当然啦,学习它当然是非常之枯燥的,但是这个枯燥是为了让自己学得更加的通透,一切都是值得的,所以下面硬着头皮开始:
先编写一个简单的代码作为字节码学习的开端,如下:
然后编译生成字节码文件:
然后双击打开它:
【提示】:当我们完全搞懂了字节码的东东之后,也就搞明白了这些反编译工具的运行机制了,为什么从.class文件能过翻译成源代码呢?
可以看到该反编译还是挺强大的,几乎跟源代码看到的是一样的,这个对于咱们来观察字节码文件不太方便,所以换用java命令来查看一下,这里用javap命令,如下:
貌似用这个命令执行之后看到的信息没有啥价值,所以这里加一个参数再来看:
xiongweideMacBook-Pro:classes xiongwei$ pwd /Users/xiongwei/Documents/workspace/IntelliJSpace/jvm_lectue/out/production/classes xiongweideMacBook-Pro:classes xiongwei$ javap -c com/jvm/bytecode/MyTest1 Compiled from "MyTest1.java" public class com.jvm.bytecode.MyTest1 { public com.jvm.bytecode.MyTest1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: iconst_1 6: putfield #2 // Field a:I 9: return public int getA(); Code: 0: aload_0 1: getfield #2 // Field a:I 4: ireturn public void setA(int); Code: 0: aload_0 1: iload_1 2: putfield #2 // Field a:I 5: return }
另外还有一个参数可以看到更加详细的字节码信息,如下:
xiongweideMacBook-Pro:classes xiongwei$ pwd /Users/xiongwei/Documents/workspace/IntelliJSpace/jvm_lectue/out/production/classes xiongweideMacBook-Pro:classes xiongwei$ javap -c com/jvm/bytecode/MyTest1 Compiled from "MyTest1.java" public class com.jvm.bytecode.MyTest1 { public com.jvm.bytecode.MyTest1(); xiongweideMacBook-Pro:classes xiongwei$ pwd /Users/xiongwei/Documents/workspace/IntelliJSpace/jvm_lectue/out/production/classes xiongweideMacBook-Pro:classes xiongwei$ javap -verbose com/jvm/bytecode/MyTest1 Classfile /Users/xiongwei/Documents/workspace/IntelliJSpace/jvm_lectue/out/production/classes/com/jvm/bytecode/MyTest1.class Last modified Aug 2, 2018; size 479 bytes MD5 checksum 4616561f95c24d6b04ea48a360437b8d Compiled from "MyTest1.java" public class com.jvm.bytecode.MyTest1 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#20 // java/lang/Object."<init>":()V #2 = Fieldref #3.#21 // com/jvm/bytecode/MyTest1.a:I #3 = Class #22 // com/jvm/bytecode/MyTest1 #4 = Class #23 // java/lang/Object #5 = Utf8 a #6 = Utf8 I #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/jvm/bytecode/MyTest1; #14 = Utf8 getA #15 = Utf8 ()I #16 = Utf8 setA #17 = Utf8 (I)V #18 = Utf8 SourceFile #19 = Utf8 MyTest1.java #20 = NameAndType #7:#8 // "<init>":()V #21 = NameAndType #5:#6 // a:I #22 = Utf8 com/jvm/bytecode/MyTest1 #23 = Utf8 java/lang/Object { public com.jvm.bytecode.MyTest1(); descriptor: ()V flags: 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: iconst_1 6: putfield #2 // Field a:I 9: return LineNumberTable: line 3: 0 line 4: 4 LocalVariableTable: Start Length Slot Name Signature 0 10 0 this Lcom/jvm/bytecode/MyTest1; public int getA(); descriptor: ()I flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #2 // Field a:I 4: ireturn LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/jvm/bytecode/MyTest1; public void setA(int); descriptor: (I)V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: iload_1 2: putfield #2 // Field a:I 5: return LineNumberTable: line 11: 0 line 12: 5 LocalVariableTable: Start Length Slot Name Signature 0 6 0 this Lcom/jvm/bytecode/MyTest1; 0 6 1 a I } SourceFile: "MyTest1.java"
下面大概来看一下打印的信息:
好,上面大致分析一下,反正目前也看不懂,接下来用二进制的分析工具来查看一下该字节码文件,有个感性的认识,在mac平台上推荐使用hexfiend,官网:http://ridiculousfish.com/hexfiend/
下载安装好既可,打开它:
将咱们的字节码文件用它打开:
目前显示的是四个字节进行分隔的:
为了更于未来的学习,将其设置为一个字节一显示,如下:
那对于这一串十六进制的魔鬼数字怎么就可以对应到javap命令所看到的结果那样呢?其实是有一定的规则的,下面先来说一下其规则:
1、使用javap -verbose命令分析一个字节码文件时,将会分析该字节码文件的魔数、版本号、常量池、类信息、类的构造方法、类中的方法信息、类变量与成员变量等信息。
2、魔数:所有的.class字节码文件的前4个字节都是魔数,魔数值为固定值:0xCAFEBABE。
回到咱们的二进制中来看是不是这样:
搜嘎,完来是这么回事,如果一个字节码文件不是这个魔数开头的话JVM则会认为是一个不合法的字节码文件。那问题来了,为啥要以“CAFEBABE”开头呢?CAFE是“咖啡”的意思,而BABE是“宝贝”的意思,翻译过来就是“咖啡宝贝”,而java本来就是咖啡的意思啦,而有个细节可以看到: