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本来就是咖啡的意思啦,而有个细节可以看到:

原文地址:https://www.cnblogs.com/webor2006/p/9404249.html