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

这个月,我着手撰写一篇文章,分析一个写得很糟糕的微评测。毕竟,我们 的程序员一直受性能困扰,我们也都想了解我们编写、使用或批评的代码的性能 特征。当我偶然间写到性能这个主题时,我经常得到这样的电子邮件:“我写的 这个程序显示,动态 frosternation 要比静态 blestification 快,与您上一 篇的观点相反!”许多随这类电子邮件而来的所谓“评测“程序,或者它们运行 的方式,明显表现出他们对于 JVM 执行字节码的实际方式缺乏基本认识。所以 ,在我着手撰写这样一篇文章(将在未来的专栏中发表)之前,我们先来看看 JVM 幕后的东西。理解动态编译和优化,是理解如何区分微评测好坏的关键(不 幸的是,好的微评测很少)。

动态编译简史

Java 应用程序的编译过程与静态编译语言(例如 C 或 C++)不同。静态编 译器直接把源代码转换成可以直接在目标平台上执行的机器代码,不同的硬件平 台要求不同的编译器。Java 编译器把 Java 源代码转换成可移植的 JVM 字节 码,所谓字节码指的是 JVM 的“虚拟机器指令”。与静态编译器不同,javac 几乎不做什么优化 —— 在静态编译语言中应当由编译器进行的优化工作,在 Java 中是在程序执行的时候,由运行时执行。

第一代 JVM 完全是解释的。JVM 解释字节码,而不是把字节码编译成机器码 并直接执行机器码。当然,这种技术不会提供最好的性能,因为系统在执行解释 器上花费的时间,比在需要运行的程序上花费的时间还要多。

即时编译

对于证实概念的实现来说,解释是合适的,但是早期的 JVM 由于太慢,迅速 获得了一个坏名声。下一代 JVM 使用即时 (JIT) 编译器来提高执行速度。按照 严格的定义,基于 JIT 的虚拟机在执行之前,把所有字节码转换成机器码,但 是以惰性方式来做这项工作:JIT 只有在确定某个代码路径将要执行的时候,才 编译这个代码路径(因此有了名称“ 即时 编译”)。这个技术使程序能启动得 更快,因为在开始执行之前,不需要冗长的编译阶段。

JIT 技术看起来很有前途,但是它有一些不足。JIT 消除了解释的负担(以 额外的启动成本为代价),但是由于若干原因,代码的优化等级仍然是一般般。为了避免 Java 应用程序严重的启动延迟,JIT 编译器必须非常迅速,这意味着 它无法把大量时间花在优化上。所以,早期的 JIT 编译器在进行内联假设 (inlining assumption)方面比较保守,因为它们不知道后面可能要装入哪个类 。

虽然从技术上讲,基于 JIT 的虚拟机在执行字节码之前,要先编译字节码, 但是 JIT 这个术语通常被用来表示任何把字节码转换成机器码的动态编译过程 —— 即使那些能够解释字节码的过程也算。

HotSpot 动态编译

HotSpot 执行过程组合了编译、性能分析以及动态编译。它没有把所有要执 行的字节码转换成机器码,而是先以解释器的方式运行,只编译“热门”代码 —— 执行得最频繁的代码。当 HotSpot 执行时,会搜集性能分析数据,用来决 定哪个代码段执行得足够频繁,值得编译。只编译执行最频繁的代码有几项性能 优势:没有把时间浪费在编译那些不经常执行的代码上;这样,编译器就可以花 更多时间来优化热门代码路径,因为它知道在这上面花的时间物有所值。而且, 通过延迟编译,编译器可以访问性能分析数据,并用这些数据来改进优化决策, 例如是否需要内联某个方法调用。

为了让事情变得更复杂,HotSpot 提供了两个编译器:客户机编译器和服务 器编译器。默认采用客户机编译器;在启动 JVM 时,您可以指定 -server 开关 ,选择服务器编译器。服务器编译器针对最大峰值操作速度进行了优化,适用于 需要长期运行的服务器应用程序。客户机编译器的优化目标,是减少应用程序的 启动时间和内存消耗,优化的复杂程度远远低于服务器编译器,因此需要的编译 时间也更少。

HotSpot 服务器编译器能够执行各种样的类。它能够执行许多静态编译器中 常见的标准优化,例如代码提升( hoisting)、公共的子表达式清除、循环展开 (unrolling)、范围检测清除、死代码清除、数据流分析,还有各种在静态编译 语言中不实用的优化技术,例如虚方法调用的聚合内联。

时间: 2024-10-28 00:36:29

Java理论与实践: 动态编译与性能测量的相关文章

Java理论与实践专题

Java理论与实践: JDK 5.0中更灵活.更具可伸缩性的锁定机制 Java理论和实践: 一个有缺陷的微基准的剖析 Java理论和实践: 理解JTS ― 平衡安全性和性能 Java理论和实践: 理解JTS ― 幕后魔术 Java理论和实践: 安全构造技术 Java理论与实践: 平衡测试,第3部分:用方面检验设计约束 Java理论与实践:平衡测试,第2部分:编写和优化bug检测器 Java理论与实践:平衡测试,第1部分:不要仅编写测试,还要编写bu Java理论与实践: 您的小数点到哪里去了?

Java理论与实践: 垃圾收集简史

Java 语言可能是使用最广泛的依赖于垃圾收集的编程语言,但是它并不是第 一个.垃圾收集已经成为了包括 Lisp.Smalltalk.Eiffel.Haskell.ML. Scheme和 Modula-3 在内的许多编程语言的一个集成部分,并且从 20 世纪 60 年代早期就开始使用了.在 Java 理论与实践的本篇文章中,Brian Goetz 描述 了垃圾收集最常用的技术. 垃圾收集的好处是无可争辩的 ―― 可靠性提高.使内存管理与类接口设计 分离,并使开发者减少了跟踪内存管理错误的时间.著

Java理论与实践: 消除bug

很多有关编程风格的建议都是为了创建高质量.可维护的代码,这很合理, 因为最容易修复 bug 的时间就是在产生 bug 之前(少量的预防措施--).遗 憾的是,只预防往往是不够的,虽然有一些精巧的工具可以帮助您创建好的代码 ,但是很少有工具可以帮助您分析.维护或提高现有代码的质量. 写线程安全的类很难,而分析现有类的线程安全性更难,增强类使其仍然保 持线程安全也很难.以隐含假定.不变式以及预期用例(虽然在开发人员的头脑 中很清晰,但是没有以设计笔记.注释或者文档的方式记录下来)的方式编写完 类之后

Java理论和实践: 一个有缺陷的微基准的剖析

即使性能不是当前项目的一个关键需求,甚至没有被标明为一个需求,通常也难于忽略性能问题,因为您可能会认为忽略性能问题将使自己成为"差劲的工程师".开发人员在以编写高性能代码为目标的时候,常常会编写小的基准程序来度量一种方法相对于另一种方法的性能.不幸的是,正如您在 December 撰写的 "动态编译与性能测量" 这期文章中所看到的,与其他静态编译的语言相比,评论用 Java 语言编写的给定惯用法(idiom)或结构体的性能要困难得多. 一个有缺陷的微基准 在我发表了

Java 理论和实践: 了解泛型 识别和避免学习使用泛型过程中的陷阱

简介: JDK 5.0 中增加的泛型类型,是 Java 语言中类型安全的一次重要改进.但是,对于初次使用泛型类型的用户来说,泛型的某些方面看起来可能不容易明白,甚至非常奇怪.在本月的"Java 理论和实践"中,Brian Goetz 分析了束缚第一次使用泛型的用户的常见陷阱.您可以通过 讨论论坛与作者和其他读者分享您对本文的看法.(也可以单击本文顶端或底端的 讨论来访问这个论坛.) 表面上看起来,无论语法还是应用的环境(比如容器类),泛型类型(或者泛型)都类似于 C++ 中的模板.但是

Java 理论与实践:变还是不变?

不变对象具有许多能更方便地使用它们的特性,包括不严格的同步需求和不必考虑数据讹误就能自由地共享和高速缓存对象引用.尽管不变性可能未必对于所有类都有意义,但大多数程序中至少有一些类将受益于不可变.在本月的 Java 理论与实践中,Brian Goetz 说明了不变性的一些长处和构造不变类的一些准则.请在附带的论坛中与作者和其他读者分享您关于本文的心得.(也可以单击文章顶部或底部的"讨论"来访问论坛.) 不变对象是指在实例化后其外部可见状态无法更改的对象.Java 类库中的 String.

Java 理论与实践: JDK 5.0 中更灵活、更具可伸缩性的锁定机制

伸缩 内容: synchronized 快速回顾 对 synchronized 的改进 比较 ReentrantLock 和 synchronized 的可伸缩性 条件变量 这不公平 结束语 参考资料 关于作者 对本文的评价 相关内容: Java 理论与实践 系列 Synchronization is not the enemy Reducing contention IBM developer kits for the Java platform (downloads) 订阅: develop

Java理论与实践: 您的小数点到哪里去了?

许多程序员在其整个开发生涯中都不曾使用定点或浮点数,可能的例外是, 偶尔在计时测试或基准测试程序中会用到.Java语言和类库支持两类非整数类型 ― IEEE 754 浮点( float 和 double ,包装类(wrapper class)为 Float 和 Double ),以及任意精度的小数( java.math.BigDecimal ).在本月的 Java 理论和实践中,Brian Goetz 探讨了在 Java 程序中使用非整数类型时一 些常碰到的陷阱和"gotcha". 虽

Java理论与实践: 构建一个更好的HashMap

ConcurrentHashMap 是 Doug Lea的 util.concurrent 包的一部分,它提供 比Hashtable 或者 synchronizedMap 更高程度的并发性.而且,对于大多数成 功的 get() 操作它会设法避免完全锁定,其结果就是使得并发应用程序有着非 常好的吞吐量.这个月,BrianGoetz 仔细分析了 ConcurrentHashMap的代码, 并探讨 Doug Lea 是如何在不损失线程安全的情况下取得这么骄人成绩的. 在7月份的那期 Java理论与实践