Java ByteCode

核能气质少年 提交于 2020-08-17 03:33:10

首先创建java文件 Testdemo.java

public class Testdemo {
    private String text = "hello world";

    public Testdemo() {
    }

    public static void main(String[] args) {
        System.out.print("hello world");
    }
}

编译

javac Testdemo.java

生成class文件 Testdemo.class

查看class 文件 打开文件 vim Testdemo.class ,然后

输入:%!xxd 就是以16进制显示class文件了,内容如下
00000000: cafe babe 0000 0034 0011 0a00 0400 0d08  .......4........
00000010: 000e 0700 0f07 0010 0100 063c 696e 6974  ...........<init
00000020: 3e01 0003 2829 5601 0004 436f 6465 0100  >...()V...Code..
00000030: 0f4c 696e 654e 756d 6265 7254 6162 6c65  .LineNumberTable
00000040: 0100 046d 6169 6e01 0016 285b 4c6a 6176  ...main...([Ljav
00000050: 612f 6c61 6e67 2f53 7472 696e 673b 2956  a/lang/String;)V
00000060: 0100 0a53 6f75 7263 6546 696c 6501 000f  ...SourceFile...
00000070: 4865 6c6c 6f57 6f72 6c64 2e6a 6176 610c  HelloWorld.java.
00000080: 0005 0006 0100 0b48 6f6c 6c6f 2057 6f72  .......Hollo Wor
00000090: 6c64 0100 0a48 656c 6c6f 576f 726c 6401  ld...HelloWorld.
000000a0: 0010 6a61 7661 2f6c 616e 672f 4f62 6a65  ..java/lang/Obje
000000b0: 6374 0021 0003 0004 0000 0000 0002 0001  ct.!............
000000c0: 0005 0006 0001 0007 0000 001d 0001 0001  ................
000000d0: 0000 0005 2ab7 0001 b100 0000 0100 0800  ....*...........
000000e0: 0000 0600 0100 0000 0100 0900 0900 0a00  ................
000000f0: 0100 0700 0000 2000 0100 0200 0000 0412  ...... .........
00000100: 024c b100 0000 0100 0800 0000 0a00 0200  .L..............
00000110: 0000 0300 0300 0400 0100 0b00 0000 0200  ................
00000120: 0c0a                                     ..
~

也可以使用Linux下的xxd命令,将二进制信息转换为16进制数据,使用方式为

xxd Testdemo.class Testdemo.txt

生成的Testdemo.txt与通过:%!xxd是一样的

二进制与16进制转换还有其他一些方式,如下:

linux下查看二进制文件
以十六进制格式输出:
od [选项] 文件
od -d 文件 十进制输出
-o 文件 八进制输出
-x 文件 十六进制输出
xxd 文件 输出十六进制

在vi命令状态下:
:%!xxd :%!od 将当前文本转化为16进制格式
:%!xxd -c 12 每行显示12个字节
:%!xxd -r 将当前文本转化回文本格式

开头的前4个字节cafe babe ,这4个字节是该文件的魔数,是一个固定值,表示此文件可以被java虚拟机接受,否则虚拟机拒绝执行此文件。

生成字节码文件:

javap -v -p Testdemo
Classfile /Users/heliming/IdeaProjectss/facf/facfjavaagent/target/classes/Testdemo.class
  Last modified 2020-7-15; size 581 bytes
  MD5 checksum 7475369414237547e1bc4b83d8623a0c
  Compiled from "Testdemo.java"
public class Testdemo
  minor version: 0
  major version: 50
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#23         // java/lang/Object."<init>":()V
   #2 = String             #24            // hello world
   #3 = Fieldref           #6.#25         // Testdemo.text:Ljava/lang/String;
   #4 = Fieldref           #26.#27        // java/lang/System.out:Ljava/io/PrintStream;
   #5 = Methodref          #28.#29        // java/io/PrintStream.print:(Ljava/lang/String;)V
   #6 = Class              #30            // Testdemo
   #7 = Class              #31            // java/lang/Object
   #8 = Utf8               text
   #9 = Utf8               Ljava/lang/String;
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               LocalVariableTable
  #15 = Utf8               this
  #16 = Utf8               LTestdemo;
  #17 = Utf8               main
  #18 = Utf8               ([Ljava/lang/String;)V
  #19 = Utf8               args
  #20 = Utf8               [Ljava/lang/String;
  #21 = Utf8               SourceFile
  #22 = Utf8               Testdemo.java
  #23 = NameAndType        #10:#11        // "<init>":()V
  #24 = Utf8               hello world
  #25 = NameAndType        #8:#9          // text:Ljava/lang/String;
  #26 = Class              #32            // java/lang/System
  #27 = NameAndType        #33:#34        // out:Ljava/io/PrintStream;
  #28 = Class              #35            // java/io/PrintStream
  #29 = NameAndType        #36:#37        // print:(Ljava/lang/String;)V
  #30 = Utf8               Testdemo
  #31 = Utf8               java/lang/Object
  #32 = Utf8               java/lang/System
  #33 = Utf8               out
  #34 = Utf8               Ljava/io/PrintStream;
  #35 = Utf8               java/io/PrintStream
  #36 = Utf8               print
  #37 = Utf8               (Ljava/lang/String;)V
{
  private java.lang.String text;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE

  public Testdemo();
    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: ldc           #2                  // String hello world
         7: putfield      #3                  // Field text:Ljava/lang/String;
        10: return
      LineNumberTable:
        line 8: 0
        line 10: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   LTestdemo;

  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     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #2                  // String hello world
         5: invokevirtual #5                  // Method java/io/PrintStream.print:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 13: 0
        line 14: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "Testdemo.java"

下面来专门讲解下字节码:

前几行表示的是类信息(这些不属于字节码文件内容)

  Last modified 2020-7-15; size 810 bytes/*修改时间*/ size 558 bytes /*文件大小*/ 
  MD5 checksum 1eebd9e50f8cfb0209c0434894ae485b/*文件的md5*/ 
  Compiled from "fddasfa.java"/* 文件的源码文件 */ 

往下看

1.魔数与Class文件版本

上边说过魔数是cafe babe
主次版本号

minor version: 0  //编译次版本号
major version: 50 //编译主版本号

参考下图得知我们的编译版本号是java 1.6

还有一些其他的标记以及解释

2.常量池 constant_pool

每个常量池的常量都用一个类型为 cp_info 的表表示,该表有 14 个值,分别是:

  #1 = Methodref          #7.#23         // java/lang/Object."<init>":()V

此常量为方法引用类型(CONSTANT_MethodHandle_info)的常量, 值指向了#7.#23的信息,也就是后边描述符 java/lang/Object."<init>":()V

#2 = String             #24            // hello world

表示该常量为字符串引用类型(CONSTANT_String_info)的常量。指向了#24的信息,也就是hello world

   #3 = Fieldref           #6.#25         // Testdemo.text:Ljava/lang/String;

此常量是一个字段引用类型CONSTANT_Fieldref_info)的常量。此类信息指向了 #6.#25的值,也就是Testdemo.text:Ljava/lang/String;

	 #24 = Utf8               hello world

此常量是一个字符串常量,转换之后是:hello world

可以看出常量池是相互复用的。

3.访问标志

在常量池结束之后,紧接着的两个字节代表访问标记(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口、是否定义为public类型、是否定义为abstract类型等。

flags: ACC_PUBLIC, ACC_SUPER

具体的标志位以及标志的含义见下表。

4.类索引、父类索引、接口索引(略)

5.字段表集合

字段表用于描述接口或者类中生命的变量,这里说的字段包括类级变量和实例级变量,但不包括在方法内部声明的局部变量。

字段表的每个字段用一个名为 field_info 的表来表示,field_info 表的数据结构如下所示:

  private java.lang.String text;
    descriptor: Ljava/lang/String; //字段描述符 为String
    flags: ACC_PRIVATE //字段访问标志 private

6.方法表集合:描述了每个方法

方法表中的每个方法都用一个 method_info 表示,其数据结构如下:

  public Testdemo();
    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: ldc           #2                  // String hello world
         7: putfield      #3                  // Field text:Ljava/lang/String;
        10: return
      LineNumberTable:
        line 8: 0
        line 10: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   LTestdemo;

下面详细解释下代码部分

stack=2, locals=1, args_size=1
	
stack:操作数栈的深度。
locals:局部变量表大小 
args_size:方法参数个数
0:aload_0: 将第一个参数压入栈中,即this(对应LocalVariableTable中Slot的0局部变量)。
1: invokespecial #1: 使用invokespecial指令调用一个特殊的初始化方法 java/lang/Object."<init>":()V 其中()代表的是这个方法的参数,后面跟的是这个方法的返回值类型,V代表void,即无返回值。
4: aload_0: 将第一个参数压入栈
5: ldc #2: 使用ldc将常量池中的字符串指针(即"Hello World")压入栈中,ldc指令表示将一个常量池中的对象压入操作数栈中.
7: putfield #3: putfiel指令消耗了栈顶的两个操作数(“Hello World",this),并将栈顶的元素放入到栈里第二个元素的对应的field内部,putfield只弹出栈内的操作数,而没有向操作数栈压回任何数据,而且执行putfield之前,栈内元素的位置也必须符合“值在上,主体在下”要求。
10: return: return仅表示方法结束,而不会像areturn一样返回栈顶元素。

下表列出了一些返回值符号对应的含义

 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     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #2                  // String hello world
         5: invokevirtual #5                  // Method java/io/PrintStream.print:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 13: 0
        line 14: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
0: getstatic #4:getstatic指令获取java/lang/System.out:Ljava/io/PrintStream的静态域,再将它压入栈中(java/io/PrintStream的实例)
3: ldc #2 :ldc将常量池中的字符串指针(即"Hello World")压入栈中,ldc指令表示将一个常量池中的对象压入操作数栈中.
5: invokevirtual #5:invokevirtual调用java/io/PrintStream.println,invokevirtual指令用于调用实例的方法
8: return:return指令返回

参考:

从 HelloWorld 看 Java 字节码文件结构 https://cloud.tencent.com/developer/article/1096582

大话+图说:Java字节码指令——只为让你懂 https://segmentfault.com/a/1190000008606277

Java字节码指令 https://www.cnblogs.com/faunjoe88/p/8126464.html

字节码一览表(墙) https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!