首先创建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
来源:oschina
链接:https://my.oschina.net/u/3730149/blog/4385511