什么是Java字節(jié)碼指令?簡(jiǎn)而言之,Java字節(jié)碼指令就是Java虛擬機(jī)能夠聽得懂、可執(zhí)行的指令,可以說是Jvm層面的匯編語言,或者說是Java代碼的最小執(zhí)行單元。
-
先來看一下Java Class的文件結(jié)構(gòu)
源文件
package com.**.lib.lib;
public class MyClass {
private String text = "hello world";
public static void main(String[] args) {
System.out.print("hello world");
}
}

cd 到class文件下,將class用文本文件打開
先看文本文件中開頭的前4個(gè)字節(jié)
cafe babe,這4個(gè)字節(jié)是該文件的魔數(shù),是一個(gè)固定值,表示此文件可以被java虛擬機(jī)接受,否則虛擬機(jī)拒絕執(zhí)行此文件。
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文件下 ,執(zhí)行命令(這里使用javac命令也可以)
javap -v -p MyClass
生成字節(jié)碼文件:
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"
下面來專門講解下字節(jié)碼:
前幾行表示的是類信息(這些不屬于字節(jié)碼文件內(nèi)容)
Last modified 2019-1-8; /*修改時(shí)間*/ size 558 bytes /*文件大小*/
MD5 checksum fc4430807406c90f9088a7bb465b432d /*文件的md5*/
Compiled from "MyClass.java" /* 文件的源碼文件 */
往下看
- 1.魔數(shù)與Class文件版本
上邊說過魔數(shù)是cafe babe
主次版本號(hào)
minor version: 0 //編譯次版本號(hào)
major version: 51 //編譯主版本號(hào)
參考下圖得知我們的編譯版本號(hào)是java 1.7

還有一些其他的標(biāo)記以及解釋

-
2.常量池 constant_pool
每個(gè)常量池的常量都用一個(gè)類型為 cp_info 的表表示,該表有 14 個(gè)值,分別是:
#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;
此常量是一個(gè)字段引用類型CONSTANT_Fieldref_info)的常量。此類信息指向了 #6.#25的值,也就是com/lucky/lib/lib/MyClass.text:Ljava/lang/String;
#24 = Utf8 hello world
此常量是一個(gè)字符串常量,轉(zhuǎn)換之后是:hello world
可以看出常量池是相互復(fù)用的。
- 3.訪問標(biāo)志
在常量池結(jié)束之后,緊接著的兩個(gè)字節(jié)代表訪問標(biāo)記(access_flags),這個(gè)標(biāo)志用于識(shí)別一些類或者接口層次的訪問信息,包括:這個(gè)Class是類還是接口、是否定義為public類型、是否定義為abstract類型等。
flags: ACC_PUBLIC, ACC_SUPER
具體的標(biāo)志位以及標(biāo)志的含義見下表。

- 4.類索引、父類索引、接口索引(略)
-
5.字段表集合
字段表用于描述接口或者類中生命的變量,這里說的字段包括類級(jí)變量和實(shí)例級(jí)變量,但不包括在方法內(nèi)部聲明的局部變量。
字段表的每個(gè)字段用一個(gè)名為 field_info 的表來表示,field_info 表的數(shù)據(jù)結(jié)構(gòu)如下所示:
private java.lang.String text;
descriptor: Ljava/lang/String; //字段描述符 為String
flags: ACC_PRIVATE //字段訪問標(biāo)志 private
-
6.方法表集合:描述了每個(gè)方法
方法表中的每個(gè)方法都用一個(gè) method_info 表示,其數(shù)據(jù)結(jié)構(gòu)如下:
public com.lucky.lib.lib.MyClass();
descriptor: ()V //方法描述 此方法是MyClass的構(gòu)造方法
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;
下面詳細(xì)解釋下代碼部分
stack=2, locals=1, args_size=1
stack:操作數(shù)棧的深度。locals:局部變量表大小 args_size:方法參數(shù)個(gè)數(shù)
0:aload_0: 將第一個(gè)參數(shù)壓入棧中,即this(對(duì)應(yīng)LocalVariableTable中Slot的0局部變量)。
1: invokespecial #1: 使用invokespecial指令調(diào)用一個(gè)特殊的初始化方法 java/lang/Object."<init>":()V 其中()代表的是這個(gè)方法的參數(shù),后面跟的是這個(gè)方法的返回值類型,V代表void,即無返回值。
4: aload_0: 將第一個(gè)參數(shù)壓入棧
5: ldc #2: 使用ldc將常量池中的字符串指針(即"Hello World")壓入棧中,ldc指令表示將一個(gè)常量池中的對(duì)象壓入操作數(shù)棧中.
7: putfield #3: putfiel指令消耗了棧頂?shù)膬蓚€(gè)操作數(shù)(“Hello World",this),并將棧頂?shù)脑胤湃氲綏@锏诙€(gè)元素的對(duì)應(yīng)的field內(nèi)部,putfield只彈出棧內(nèi)的操作數(shù),而沒有向操作數(shù)棧壓回任何數(shù)據(jù),而且執(zhí)行putfield之前,棧內(nèi)元素的位置也必須符合“值在上,主體在下”要求。
10: return: return僅表示方法結(jié)束,而不會(huì)像areturn一樣返回棧頂元素。
下表列出了一些返回值符號(hào)對(duì)應(yīng)的含義

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的靜態(tài)域,再將它壓入棧中(java/io/PrintStream的實(shí)例)
3: ldc #2 :ldc將常量池中的字符串指針(即"Hello World")壓入棧中,ldc指令表示將一個(gè)常量池中的對(duì)象壓入操作數(shù)棧中.
5: invokevirtual #5:invokevirtual調(diào)用java/io/PrintStream.println,invokevirtual指令用于調(diào)用實(shí)例的方法
8: return:return指令返回
參考:
從 HelloWorld 看 Java 字節(jié)碼文件結(jié)構(gòu)
大話+圖說:Java字節(jié)碼指令——只為讓你懂
Java字節(jié)碼指令
字節(jié)碼一覽表(墻)


