java内联函数,JVM中的步骤内联
JVM中的方法內聯
在C++中,可以明確定義內聯函數,使用inline關鍵字。在Java中不能定義內聯函數,但是方法的內聯在JIT編譯中還是存在的,只不過是JIT自動優化的,我們無法在寫代碼的時候指定。
所謂內聯函數就是指函數在被調用的地方直接展開,編譯器在調用時不用像一般函數那樣,參數壓棧,返回時參數出棧以及資源釋放等,這樣提高了程序執行速度。 一般函數的調用時,JVM會自動新建一個堆棧框架來處理參數和下一條指令的地址,當執行完函數調用后再撤銷該堆棧。
寫一段代碼來測試一下。
public class Test {
public static void foo() {
boolean t = true;
boolean f = false;
System.out.println(t == f);
}
public static void main(String[] args) {
foo();
}
}
看一下這段代碼的字節碼信息,使用命令
javap -c -s -l -verbose Test
關鍵部位的字節碼信息如下
public static void foo();
Signature: ()V
LineNumberTable:
line 5: 0
line 6: 2
line 7: 4
line 8: 20
LocalVariableTable:
Start Length Slot Name Signature
2 19 0 t Z
4 17 1 f Z
Code:
Stack=3, Locals=2, Args_size=0
0: iconst_1
1: istore_0
2: iconst_0
3: istore_1
4: getstatic #15; //Field java/lang/System.out:Ljava/io/PrintStream;
7: iload_0
8: iload_1
9: if_icmpne 16
12: iconst_1
13: goto 17
16: iconst_0
17: invokevirtual #21; //Method java/io/PrintStream.println:(Z)V
20: return
LineNumberTable:
line 5: 0
line 6: 2
line 7: 4
line 8: 20
LocalVariableTable:
Start Length Slot Name Signature
2 19 0 t Z
4 17 1 f Z
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 16
locals = [ int, int ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ int, int ]
stack = [ class java/io/PrintStream, int ]
public static void main(java.lang.String[]);
Signature: ([Ljava/lang/String;)V
LineNumberTable:
line 11: 0
line 12: 3
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 args [Ljava/lang/String;
Code:
Stack=0, Locals=1, Args_size=1
0: invokestatic #33; //Method foo:()V
3: return
LineNumberTable:
line 11: 0
line 12: 3
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 args [Ljava/lang/String;
可以看到在字節碼中是不能反映出方法內聯的,方法內聯是在JIT編譯時發生的,Oracle對方法內聯的舉例(傳送門:http://java.sun.com/developer/technicalArticles/Networking/HotSpot/inlining.html),JVM會做出優化。要發現該方法是否被內聯可以使用如下命令:
java -Xrunhprof:cpu=times InlineMe
在本文中我沒有進行這個測試,如果想看這個測試結果,可以到這里來看。(傳送門:http://spring8314.iteye.com/blog/139299)
也可以使用參數:(這個參數只能在debug mode下使用,可以參見http://www.oracle.com/technetwork/java/javase/tech/exactoptions-jsp-141536.html,建議使用fastdebug來玩,傳送門:http://agapple.iteye.com/blog/1056599)
-XX:+PrintInlining
打印出來的內容如下
@ 0 org.dothwinds.test.Test::foo (21 bytes)
可以看到log中存在foo方法。后面的21字節表示字節碼所占用的字節。那么用javap可以看到foo方法的字節碼正好占用21字節
Code:
Stack=3, Locals=2, Args_size=0
0: iconst_1
1: istore_0
2: iconst_0
3: istore_1
4: getstatic #15; //Field java/lang/System.out:Ljava/io/PrintStream;
7: iload_0
8: iload_1
9: if_icmpne 16
12: iconst_1
13: goto 17
16: iconst_0
17: invokevirtual #21; //Method java/io/PrintStream.println:(Z)V
20: return
我們將這段代碼反匯編x86asm來看看。
java -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+Pr
intAssembly org/dothwinds/test/Test >log.log
到當前目錄下找到這個log文件,將有意義的代碼提取出來。
Code:
[Disassembling for mach='i386']
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} 'main' '([Ljava/lang/String;)V' in 'org/dothwinds/test/Test'
# parm0: ecx = '[Ljava/lang/String;'
# [sp+0x20] (sp of caller)
;; block B6 [0, 0]
0x01cd3930: mov %eax,-0x8000(%esp)
0x01cd3937: push %ebp
0x01cd3938: sub $0x18,%esp ;*invokestatic foo
; - org.dothwinds.test.Test::main@0 (line 11)
;; block B0 [0, 3]
;; 10 move [obj:0x0|L] [edx|L] [patch_normal] [bci:4]
0x01cd393b: nop
0x01cd393c: nop
0x01cd393d: nop
0x01cd393e: nop
0x01cd393f: nop
0x01cd3940: jmp 0x01cd3990 ; {no_reloc}
;; 12 move [Base:[edx|L] Disp: 2147483647|L] [ecx|L] [patch_normal] [bci:4]
0x01cd3945: nop
0x01cd3946: nop
0x01cd3947: nop
0x01cd3948: jmp 0x01cd39bb ; implicit exception: dispatches to 0x01cd399a
0x01cd394d: nop ;*getstatic out
; - org.dothwinds.test.Test::foo@4 (line 7)
; - org.dothwinds.test.Test::main@0 (line 11)
0x01cd394e: cmp (%ecx),%eax ; implicit exception: dispatches to 0x01cd39c5
0x01cd3950: mov $0x0,%edx ;*invokevirtual println
; - org.dothwinds.test.Test::foo@17 (line 7)
; - org.dothwinds.test.Test::main@0 (line 11)
0x01cd3955: nop
0x01cd3956: mov $0xffffffff,%eax ; {oop(NULL)}
0x01cd395b: call 0x01c0b210 ; OopMap{off=48}
;*invokevirtual println
; - org.dothwinds.test.Test::foo@17 (line 7)
; - org.dothwinds.test.Test::main@0 (line 11)
; {virtual_call}
0x01cd3960: add $0x18,%esp
0x01cd3963: pop %ebp
0x01cd3964: test %eax,0x260100 ; {poll_return}
0x01cd396a: ret
這段代碼之所以能看出有內聯特征是因為(借用撒迦(http://rednaxelafx.iteye.com/)的話):
0x01cd395b: call 0x01c0b210 ; OopMap{off=48}
;*invokevirtual println
; - org.dothwinds.test.Test::foo@17 (line 7)
; - org.dothwinds.test.Test::main@0 (line 11)
; {virtual_call}
每行右邊分號后面是注釋。它會顯示當前機器指令對應的原本的Java字節碼是什么、字節碼是從哪里來的。
可以看到這里call指令對應的字節碼原本是個invokevirtual,而它原本是在foo()方法中的,并且被main()方法內聯了。
如果不想進行方法內聯,可以使用參數:
-XX:CompileCommand=dontinline,org/dothwinds/test/Test,foo
但是需要注意的是,C1(Client模式)下是不檢查dontinline的,解決方法可以見帖子:傳送門(http://hllvm.group.iteye.com/group/topic/26381)
如果嫌這種方式麻煩,那只能使用C2(Server模式)來玩了。
最后要十分感謝撒迦的幫助和指點。
總結
以上是生活随笔為你收集整理的java内联函数,JVM中的步骤内联的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Alfresco Community 7
- 下一篇: 计算机英语电子词典,电子辞典时代的英语学