什么是Java字节码指令?简而言之,Java字节码指令就是Java虚拟机能够听得懂、可执行的指令,可以说是Jvm层面的汇编语言,或者说是Java代码的最小执行单元。
-
先来看一下Java Class的文件结构
源文件
package com.**.lib.lib;
public class MyClass {
private String text = "hello world";
public static void main(String[] args) {
System.out.print("hello world");
}
}
class文件
cd 到class文件下,将class用文本文件打开
先看文本文件中开头的前4个字节
cafe babe
,这4个字节是该文件的魔数,是一个固定值,表示此文件可以被java虚拟机接受,否则虚拟机拒绝执行此文件。
cafe babe 0000 0033 0022 0a00 0600 1409
0015 0016 0800 170a 0018 0019 0700 1a07
001b 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 1b4c 636f 6d2f 6c75
636b 792f 6c69 622f 6c69 622f 4d79 436c
6173 733b 0100 046d 6169 6e01 0016 285b
4c6a 6176 612f 6c61 6e67 2f53 7472 696e
673b 2956 0100 0461 7267 7301 0013 5b4c
6a61 7661 2f6c 616e 672f 5374 7269 6e67
3b01 000a 536f 7572 6365 4669 6c65 0100
0c4d 7943 6c61 7373 2e6a 6176 610c 0007
0008 0700 1c0c 001d 001e 0100 0b68 656c
6c6f 2077 6f72 6c64 0700 1f0c 0020 0021
0100 1963 6f6d 2f6c 7563 6b79 2f6c 6962
2f6c 6962 2f4d 7943 6c61 7373 0100 106a
6176 612f 6c61 6e67 2f4f 626a 6563 7401
0010 6a61 7661 2f6c 616e 672f 5379 7374
656d 0100 036f 7574 0100 154c 6a61 7661
2f69 6f2f 5072 696e 7453 7472 6561 6d3b
0100 136a 6176 612f 696f 2f50 7269 6e74
5374 7265 616d 0100 0570 7269 6e74 0100
1528 4c6a 6176 612f 6c61 6e67 2f53 7472
696e 673b 2956 0021 0005 0006 0000 0000
0002 0001 0007 0008 0001 0009 0000 002f
0001 0001 0000 0005 2ab7 0001 b100 0000
0200 0a00 0000 0600 0100 0000 0300 0b00
0000 0c00 0100 0000 0500 0c00 0d00 0000
0900 0e00 0f00 0100 0900 0000 3700 0200
0100 0000 09b2 0002 1203 b600 04b1 0000
0002 000a 0000 000a 0002 0000 0005 0008
0006 000b 0000 000c 0001 0000 0009 0010
0011 0000 0001 0012 0000 0002 0013
cd 到class文件下 ,执行命令(这里使用javac命令也可以)
javap -v -p MyClass
生成字节码文件:
Classfile /Users/.../MyClass.class
Last modified 2019-1-8; size 614 bytes
MD5 checksum 24c88198062311ff001f795f779bd2b1
Compiled from "MyClass.java"
public class com.lucky.lib.lib.MyClass
minor version: 0
major version: 51
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 // com/lucky/lib/lib/MyClass.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 // com/lucky/lib/lib/MyClass
#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 Lcom/lucky/lib/lib/MyClass;
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 args
#20 = Utf8 [Ljava/lang/String;
#21 = Utf8 SourceFile
#22 = Utf8 MyClass.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 com/lucky/lib/lib/MyClass
#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 com.lucky.lib.lib.MyClass();
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 3: 0
line 4: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/lucky/lib/lib/MyClass;
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 7: 0
line 8: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
SourceFile: "MyClass.java"
下面来专门讲解下字节码:
前几行表示的是类信息(这些不属于字节码文件内容)
Last modified 2019-1-8; /*修改时间*/ size 558 bytes /*文件大小*/
MD5 checksum fc4430807406c90f9088a7bb465b432d /*文件的md5*/
Compiled from "MyClass.java" /* 文件的源码文件 */
往下看
- 1.魔数与Class文件版本
上边说过魔数是cafe babe
主次版本号
minor version: 0 //编译次版本号
major version: 51 //编译主版本号
参考下图得知我们的编译版本号是java 1.7
主次版本号
还有一些其他的标记以及解释
-
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 // com/lucky/lib/lib/MyClass.text:Ljava/lang/String;
此常量是一个字段引用类型CONSTANT_Fieldref_info)的常量。此类信息指向了 #6.#25的值,也就是com/lucky/lib/lib/MyClass.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 com.lucky.lib.lib.MyClass();
descriptor: ()V //方法描述 此方法是MyClass的构造方法
flags: ACC_PUBLIC //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 3: 0
line 4: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/lucky/lib/lib/MyClass;
下面详细解释下代码部分
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 7: 0
line 8: 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 字节码文件结构
大话+图说:Java字节码指令——只为让你懂
Java字节码指令
字节码一览表(墙)
网友评论