1. 程式人生 > >jvm原理(29)構造方法與靜態程式碼塊位元組碼指令詳解

jvm原理(29)構造方法與靜態程式碼塊位元組碼指令詳解

上一節解析完了常量池,接下來是訪問標記
這裡寫圖片描述
00 21 : ACC_SUPPER + ACC_PUBLIC
緊接著是類的名字,2個位元組:00 05 是5號常量 【#5 = Class #49 // com/twodragonlake/jvm/bytecode/MyTest2】
父類的名字,佔2個位元組:00 0D 是13號常量:【#13 = Class #59 // java/lang/Object】
介面的資訊:00 00 沒有介面。
成員變數資訊:00 03 有三個成員變數。
第一個欄位:
訪問標記:00 00 為預設訪問標記。
名字索引:00 0E 是14號常量:【#14 = Utf8 str】
描述符索引:00 0F 是15號常量:【#15 = Utf8 Ljava/lang/String;】
欄位屬性數量:00 00 沒有屬性
第二個欄位:
訪問標記:00 02 私有的,private
名字索引:00 10 是16號常量:【#16 = Utf8 x】
描述符索引:00 11 是17號常量:【 #17 = Utf8 I】
欄位屬性數量:00 00 沒有欄位屬性
第三個欄位:
訪問標記:00 09 是public + static
名字索引:00 12是18號常量:【#18 = Utf8 in】
描述符索引:00 13 是19號常量:【#19 = Utf8 Ljava/lang/Integer;】
欄位屬性數量:00 00 沒有
然後是方法:
方法的數量:00 06 是6個方法:
init 、 main、setX、test、test2、clinit(靜態程式碼塊)
第一方法:
訪問標記:00 01 是public
名字索引:00 14 是20號常量:【#20 = Utf8 <init>


描述符索引:00 15 是21號常量:【#21 = Utf8 ()V】
方法屬性數量:00 01 包含一個屬性
第一個屬性:
屬性名字索引:00 16是22號索引:【#22 = Utf8 Code】
屬性長度:00 00 0 42 為66個長度,預設構造方法完成了成員變數的賦值,注意只是對非靜態的賦值:

 0 aload_0
 1 invokespecial #1 <java/lang/Object.<init>> 呼叫父類構造器
 4 aload_0
 5 ldc #2 <Welcome>  載入字串Welcome
 7
putfield #3 <com/twodragonlake/jvm/bytecode/MyTest2.str> 把字元竄Welcome賦值給str變數 10 aload_0 11 iconst_5 載入整數5 12 putfield #4 <com/twodragonlake/jvm/bytecode/MyTest2.x> 把整數5賦值給x變數 15 return 方法返回

如果我們自己宣告一個構造方法,是不是也會存在對成員變數的賦值?我們加入一個有引數的構造方法:

    public MyTest2(){

    }

    public MyTest2(int i){

    }

這裡寫圖片描述
這裡寫圖片描述

可以看到預設的無參構造器和有引數的構造器位元組碼是一樣的,都會對成員變數進行賦值。
其他的方法的位元組碼解析和之前的程式大致相同,不再熬述。
著重說一下test方法:

 0 aload_1
 1 dup
 2 astore_2
 3 monitorenter  
 4 getstatic #10 <java/lang/System.out>
 7 ldc #11 <hello world>
 9 invokevirtual #12 <java/io/PrintStream.println>
12 aload_2
13 monitorexit
14 goto 22 (+8)
17 astore_3
18 aload_2
19 monitorexit
20 aload_3
21 athrow
22 return

monitorenter 是synchronized的監視器加鎖的地方,oracle的官方doc:
monitorenter
Operation
Enter monitor for object
進入物件的監視器
Format

monitorenter
Forms
monitorenter = 194 (0xc2)

Operand Stack
…, objectref →

Description
The objectref must be of type reference.
監視的物件必須是引用型別
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
每一個物件都有一個監視器,如果一個monitor 是擁有者那麼它就獲得了鎖,執行緒獲得monitorenter 的使用權遵循下邊的過程:
If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
如果monitor關聯物件的進入次數是0,當前執行緒進入monitor並且設定進入次數是1,那麼接下來這個執行緒就是這個monitor的擁有者。
If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
如果一個執行緒已經是關聯物件的monitor的擁有者,那麼執行緒再次進入monitor,會使得進入次數加1
If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.
如果另外一個執行緒已經是關聯物件的monitor 的擁有者,那麼當前執行緒會一直阻塞到進入次數為0,才能再次嘗試獲取monitor 的使用權。

monitorexit
monitorexit
Operation
Exit monitor for object
為了退出物件的monitor
Format

monitorexit
Forms
monitorexit = 195 (0xc3)

Operand Stack
…, objectref →

Description
The objectref must be of type reference.
關聯的物件必須是引用型別的。
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
當前執行monitorexit 的執行緒必須是關聯物件例項物件的引用上的monitor 的擁有者。
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
當前執行緒減1個進入次數,正對與關聯物件上的monitor 的進入此時,如果減一之後變成0,那麼當前執行緒退出monitor ,不再是擁有者,其他阻塞的執行緒此時可以被允許嘗試獲取擁有權。

回到我們的位元組碼,看一下clinit 對靜態程式碼塊的操作:

0 bipush 10
2 invokestatic #8 <java/lang/Integer.valueOf>
5 putstatic #9 <com/twodragonlake/jvm/bytecode/MyTest2.in> 對靜態變數in進行賦值
8 return

如果我們加入一個static程式碼塊,那麼clinit 會有什麼變化?

    static {
        System.out.println("test");
    }

clinit程式碼塊:

 0 bipush 10
 2 invokestatic #8 <java/lang/Integer.valueOf>
 5 putstatic #9 <com/twodragonlake/jvm/bytecode/MyTest2.in>
 8 getstatic #10 <java/lang/System.out>
11 ldc #13 <test>
13 invokevirtual #12 <java/io/PrintStream.println>
16 return

可以看到靜態的程式碼塊的內容被加到了 clinit裡邊去了,不管有多少個靜態程式碼塊 都會合併到clinit裡邊去。