`
huxi
  • 浏览: 17152 次
  • 性别: 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_高级特性与最佳实践

    / 301 11.3.2 公共子表达式消除 / 305 11.3.3 数组边界检查消除 / 307 11.3.4 方法内联 / 307 11.3.5 逃逸分析 / 309 11.4 Java与C/C++的编译器对比 / 311 11.5 本章小结 / 313 第五部分 高效并发 第12章 ...

    内联Java:通过Haskell模块中的内联Java代码进行HaskellJava互操作

    内联Java:从Haskell调用任何JVM函数 Haskell标准包括本机外部功能接口(FFI)。 使用它可能会有点麻烦,GHC中仅实现了C支持。 inline-java使您可以直接从Haskell调用任何JVM函数,而无需使用FFI编写自己的外部导入...

    JProfiler11破解.rar

    自动检测将在当前配置的JVM中生成数据的跟踪类型 异步跟踪状态显示在具有相关操作的调用树视图上方 检测非配置类中的调用是否会创建异步调用并提供记录它 Kotlin协同程序的异步跟踪;基于IntelliJ IDEA的全新明暗...

    kotlin-inline-logger:记录器通过Kotlin的内联类和函数促进延迟评估的日志调用

    Kotlin在线记录器 记录器通过Kotlin的内联和来促进惰性评估...在JVM上, kotlin-inline-logger提供提供委托给内联函数。 从现有SLF4J解决方案迁移的用户将发现对记录功能的现有调用,如logger.info("Example")现已标记

    Kotlin语言文档 中文高清免费版

    内联函数 81 协程 84 其他 88 解构声明 88 集合 ...

    groovy_jvm_talk

    内联列表,地图声明 对象实例化没有隐私修饰符会发生什么? 操纵集合 Java互操作 确定要运行的方法:编译时间与运行时 时髦的弦 基准测试-运行时绑定与静态绑定 议程 简介(约翰) 设置上下文。 什么是Groovym趋势...

    Java虚拟机

    11.3.4 方法内联 11.3.5 逃逸分析 11.4 Java与CC++的编译器对比 11.5 本章小结 第五部分 高效并发 第12章 Java内存模型与线程 12.1 概述 12.2 硬件的效率与一致性 12.3 Java内存模型 12.3.1 主内存与工作...

    Java理论与实践:动态编译与性能测量

    要做到这样,需要“预热”JVM,编译器在为执行计时之前,就有足够的运行解释的代码,并用编译的代码替换解释代码。编译器要进行主动的内联决策,才能得到更高的性能,然后当这些决策依据的假设不再有效时,就会收回...

    HikariCP-benchmark:JDBC连接池的JHM基准

    我们已经了解到,使用Dead Code Elimination(DCE),锁合并,内联,循环展开,栈上替换(OSR)和无数其他技巧的JVM基准测试使大多数尝试完全进行基准测试无效-包括我们自己的原始基准。 阅读[即使是聪明的]在JVM上...

    DragonConsole:DragonConsole是基于Java的跨平台终端仿真器

    内联,允许用户直接在控制台中键入(用于控制台外观)或分隔输入,该输入使用控制台底部的“输入区域”进行输入。 FileProcessor,用于快速轻松地读取纯文本文件并将其转换为String。 InputScript,它允许程序员...

    lightweight-streams:Streams for Java,Java 8 风格,仅使用 lambdas

    Java 轻量级流 ... 这个库的目的是让 JVM 更容易研究 lambda 内联决策。 测试 # run unit tests mvn -q test # build benchmarks über-jar mvn clean package -Dskiptests # run benchmarks java -Xmx2g -X

    【白雪红叶】JAVA学习技术栈梳理思维导图.xmind

    内联优化 系统优化 cache 延迟计算 数据预读 异步 轮询与通知 内存池 模块化 工程架构能力 开发语言 运维与监控 监控 系统监控 日志监控 流量监控 接口监控 数据库监控 业务监控 性能监控 告警...

    java8stream源码-lambda-world:Kotlin代码库、学习、再学习、示例等

    中,只是保持简单和干净。 知识范围从 collection ie foreach, stream 到一些 high like 。 运算符的覆盖 Foreach 使用 JVM foreach循环一些输出。 使用 JVM stream().foreach循环一些输出,仅限 Java8。 在stream上...

    i18n4k:Kotlin的国际化

    i18n4k是Kotlin的多平台(JVM,JS,本机)库和代码生成器,用于处理程序中的国际化(i18n)。 它提供 Locale类,用于存储所选语言 LocalizedString类,用于存储对可翻译消息的引用。 toString()将以当前选择的语言/...

    redpanda:Redpanda是现代应用程序的实时引擎

    兼容Kafka:registered:,不需要Zookeeper:registered:,不需要JVM,也不需要更改代码。 使用所有您喜欢的开源工具-快10倍。 我们正在为现代应用程序构建实时流引擎-从企业到在她的笔记本电脑上为React应用程序制作...

    kodluyoruz-scala:关于Kodluyoruz的Scala讲座的内容

    参照透明性-内联所有内容! 2. Scala基础 2.1。 介绍 使用功能强大的打字系统进行静态打字 多范例,两全其美,OOP + FP 在JVM上运行,可以进行Java互操作 现代,简洁,时尚 2.2。 你好,世界 object Application ...

    kotlinx.logger-simple:多平台最小开销记录器

    具有内联功能的最小开销记录器 如何使用 消息记录 import kotlinx.logger.simple.* debug { " Your message " } d { " Your message " } warning { " Your message " } w { " Your message " } info { " Your ...

Global site tag (gtag.js) - Google Analytics