初识JVM中的方法内联

在C++中,可以明确定义内联函数,使用inline关键字。在Java中不能定义内联函数,但是方法的内联在JIT编译中还是存在的,只不过是JIT自动优化的,我们无法在写代码的时候指定。 

所谓内联函数就是指函数在被调用的地方直接展开,编译器在调用时不用像一般函数那样,参数压栈,返回时参数出栈以及资源释放等,这样提高了程序执行速度。 一般函数的调用时,JVM会自动新建一个堆栈框架来处理参数和下一条指令的地址,当执行完函数调用后再撤销该堆栈。  

写一段Java代码来测试一下。 

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代码  

javap -c -s -l -verbose Test  

关键部位的字节码信息如下 
Bytecode代码 

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会做出优化。要发现该方法是否被内联可以使用如下命令: 

Runtime option代码 

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) 

Runtime option代码  

-XX:+PrintInlining  

打印出来的内容如下 

Inline log代码  

@ 0   org.dothwinds.test.Test::foo (21 bytes)  

可以看到log中存在foo方法。后面的21字节表示字节码所占用的字节。那么用javap可以看到foo方法的字节码正好占用21字节 

Bytecode代码 

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文件,将有意义的代码提取出来。 

X86 asm代码 

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      

X86 asm代码 

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()方法内联了。 

如果不想进行方法内联,可以使用参数: 

Runtime option代码 

-XX:CompileCommand=dontinline,org/dothwinds/test/Test,foo  

但是需要注意的是,C1(Client模式)下是不检查dontinline的,解决方法可以见帖子:传送门(http://hllvm.group.iteye.com/group/topic/26381) 
如果嫌这种方式麻烦,那只能使用C2(Server模式)来玩了。 

发布了18 篇原创文章 · 获赞 33 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Dothwinds/article/details/104763663
今日推荐