`
huxi
  • 浏览: 17678 次
  • 性别: Icon_minigender_1
  • 来自: cc
社区版块
存档分类
最新评论

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模式)来玩了。
最后要十分感谢撒迦的帮助和指点。
0
0
分享到:
评论

相关推荐

    Java动态绑定和内联

    方法表是一个指向方法区中方法指针的数组,其中包含了需要动态绑定的实例方法。当调用一个实例方法时,JVM会通过对象引用找到对象的实际类型,然后在方法表中找到对应的方法指针,从而执行相应的方法。 内联,是...

    jvm特性与java特性

    JVM的性能优化还包括对方法内联、逃逸分析等技术的应用。 5. 线程模型:JVM提供了一套标准的线程模型,使得Java程序能够利用多线程来执行并发任务。JVM的线程模型与操作系统的本地线程直接映射,这有助于更好地利用...

    JVM指令集.zip

    这个过程涉及到指令集的动态优化,如栈深度分析、循环展开、方法内联等。 5. **理解JVM指令集的意义** 熟悉JVM指令集有助于深入理解Java程序的运行机制,对于性能优化、问题排查、JVM调优等都有重要意义。例如,...

    JVM优化3(Tomcat参数调优,JVM参数调优,jvm字节码,代码优化).pdf

    虽然这部分内容没有在提供的文档中详细展开,但通常包括通过一些工具分析字节码,找出性能瓶颈,进行指令重组、方法内联、死码删除等操作,以此来提升程序运行效率。 4. 代码优化 代码优化是提高Java应用性能的一...

    JVM性能调优

    - **内联与去内联**:适当使用方法内联可以提高性能。 #### 工具与技术 - **JConsole**:内置的监视工具,用于监控JVM的运行状态。 - **VisualVM**:提供丰富的图形界面,帮助分析JVM性能问题。 - **JProfiler** ...

    JVM工作原理

    为了提升性能,JVM采用了多种优化技术,如逃逸分析、标量替换、栈上替换、内联等。这些技术能帮助JVM更好地理解和优化代码。 7. **工具支持** JVM提供了丰富的工具,如JConsole、VisualVM、JProfiler等,用于监控...

    从 0 开始带你成为JVM实战高手【完整版】

    随着Java技术栈在企业级应用中的广泛采用,深入理解Java虚拟机(JVM)对于提升开发效率、优化系统性能至关重要。本教程旨在通过96个章节的内容,帮助读者从零开始掌握JVM的核心原理及其实战技巧。 #### 二、基础...

    JVM底层原理课件PPT

    在PPT中还提到了C1/C2 JIT编译器,这是JVM中用于提高程序执行效率的重要组成部分。 - **C1编译器**:主要负责即时编译,将热点代码转换为本地机器码,注重编译时间与执行效率之间的平衡。 - **C2编译器**:也称为...

    JVM知识图谱.pdf

    - **优化**:编译时进行多种优化技术,如内联、类型检查消除等。 #### 十二、控制阈值提升性能 - **阈值设置**:通过调整阈值参数,控制何时触发JIT编译。 - **性能提升**:合理设置阈值可以避免不必要的编译开销...

    JAVA应用JVM原理及参数调优深入讲解视频.rar

    -XX:MaxInlineSize控制方法内联的最大字节码长度。 5. 调优工具:如JVisualVM、JConsole、JMX等可以帮助监控和分析JVM的运行状态,提供调优依据。 6. 并发和线程:-XX:ParallelGCThreads设定并行GC的线程数,-XX:...

    jvm反汇编插件

    8. **优化策略分析**:通过反汇编插件,开发者可以观察JIT是如何对代码进行优化的,例如,是否正确地识别出了循环并进行了优化,或者是否进行了方法内联等。这有助于指导代码编写,使其更易于JIT优化。 总之,JVM反...

    java虚拟机深入JVM内核—原理、诊断与优化视频教程网盘下载

    5. 方法内联:减少调用开销,提升性能。 五、诊断工具 1. jconsole:图形化监控JVM状态,包括内存、线程、类加载等。 2. jvisualvm:丰富的分析工具,包含监视、线程分析、内存分析和性能采样等功能。 3. jmap:...

    Java虚拟机(JVM)

    为了提高性能,JVM采用了多种优化技术,包括即时编译(JIT)、热点代码检测和内联缓存等。其中,即时编译技术能够将频繁执行的字节码编译成本地机器代码,显著提升执行速度。 #### 五、JVM与操作系统 JVM在不同的...

    jvm高级特性第二版

    此外,JVM的编译优化,如即时编译(Just-In-Time,JIT)和方法内联等,也是提高代码执行效率的重要手段。了解JVM如何对热点代码进行优化,可以帮助我们编写出更高效的Java代码。 最后,书中还会涉及动态语言支持、...

    JVM 50道面试题和答案.docx

    如果一个对象在方法内部创建,并且没有逃逸出方法(即没有被其他方法引用),JVM可能会进行栈上分配(栈内联)或者标量替换优化,使得对象不在堆上分配内存。 【垃圾收集】 JVM采用可达性分析算法来判断对象是否为...

    JVM与性能优化知识点的整理.docx

    2. **Java栈(线程私有)**:每个线程都有自己的Java栈,用于存储方法调用过程中的局部变量表、操作数栈、动态链接和方法出口等信息。当方法调用结束时,对应的栈帧也会被弹出。 3. **本地方法栈(线程私有)**:...

    JVM高级特性与最佳实践

    在JVM内存模型方面,本书讲解了JVM内存区域的划分,包括堆(Heap)、栈(Stack)、方法区(Method Area)、程序计数器(Program Counter)和本地方法栈(Native Method Stack)等部分。作者详细解释了这些内存区域的...

    最新全JVM优化及面试热点分析.zip

    - **方法内联**:如何通过内联减少调用开销,提高程序性能。 - **逃逸分析**:理解对象生命周期,优化局部变量和短生命周期对象的分配。 5. **JVM监控与诊断工具** - **JConsole** 和 **VisualVM**:使用这些...

    Kotlin中关于内联函数的一些理解分享

    在Kotlin编程语言中,内联函数(inline functions)是一个重要的特性,它允许开发者通过编译时的代码膨胀来提高程序的运行效率。内联函数的主要目的是消除函数调用时的开销,如堆栈管理和参数传递,这些操作在运行时...

    JVM与性能优化知识点整理.zip

    了解编译优化的机制,如方法内联、逃逸分析等,有助于提升程序性能。 7. **性能监控与诊断工具**:如jconsole、jvisualvm、jmap、jhat等,用于监控JVM状态、分析内存、生成堆转储文件,以及诊断线程死锁等问题。 8...

Global site tag (gtag.js) - Google Analytics