JRockit权威指南:深入理解JVM(txt+pdf+epub+mobi电子书下载)


发布时间:2020-08-02 19:09:11

点击下载

作者:(瑞士) 马库斯·希尔特(Marcus Hirt)(瑞典)马库斯·拉杰格伦(Marcus Lagergren)

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

JRockit权威指南:深入理解JVM

JRockit权威指南:深入理解JVM试读:

前言

机缘巧合促成了本书的出版。

那时,互联网还没有在世界范围内普及,我们也还只是高中生,经常混迹于同一个BBS,在讨论数学问题的过程中结识了对方,成为了好友,并将这份友情延伸到了生活和合作的软件项目中。后来,我们又共同进入了位于斯德哥尔摩的瑞典皇家理工学院(KTH)学习。

在KTH,我们结识了更多的朋友。在第三学年的数据库系统课程中,我们找到了足够多志同道合的人,准备干点事业。最终,我们决定成立一家名为Appeal Software Solutions(其首字母缩写为A.S.S.,当时看来绝对是一个完美的名字)的咨询公司。我们中有些人是半工半读的,所以预留了部分收入,以便当所有成员毕业后可以使公司步入正轨。我们的长期目标是公司可以开发产品,而不仅仅是做咨询,但当时我们还不知道到底要开发什么。

1997年,由于在Sun公司赞助的大学生竞赛中胜出,Joakim Dahlstedt、Fredrik Stridsman和Mattias Jo?lson得以参加当年的JavaOne大会。有意思的是,第二年,他们又胜出了。

一切都源于我们的3位英雄在1997年和1998年参加的两届JavaOne大会。在会上,他们注意到,Sun公司的自适应JVM——HotSpot虽然在当时被誉为能够彻底解决Java性能问题的终极JVM,但在这两年里却没有什么实质性的进步。那时的Java主要是解释执行的,市场上有一些针对Java的静态编译器,可以生成运行速度快于字节码的静态代码,但是这从根本上违反了Java的语义。正如本书反复强调的,到目前为止,自适应解决方案在运行时具有远超静态解决方案的潜力,但实现起来也更困难。

1998年,HotSpot没什么动作,年轻气盛的我们不禁问道:“这很难吗?看我们做一个更好、更快的自适应虚拟机出来!”我们专业背景不错,而且认为有了明确的方向,于是就开工了。尽管后来的实践证明了挑战比我们预期的更大,但我们想提醒读者的是,当时是1998年,Java在服务器端的腾飞才刚刚开始,J2EE刚刚出现,几乎没人听说过JSP。因此,我们所涉及的问题领域小得多。

我们最初计划用一年时间实现一个JVM的预览版,同时继续提供咨询服务来保证JVM的持续开发。最初,新JVM的名字是RockIT,结合了Rock and Roll(摇滚)、Rock Solid(坚如磐石)和IT三者的意思。后来由于注册商标的原因,又在名字前面加了一个字母J。

在经历了初期的几次失败后,我们需要寻找风投。当然,向投资人解释清楚为什么投资一款自适应JVM能够赚钱(同时期的其他竞争对手都是免费提供的),是一大难题。这不仅仅因为当时是1998年,更重要的因素是,投资人还无法理解这种既不需要给用户发广告短信,也不需要发送电子邮件订单的商业模式。

最终,我们获得了风投,并在2000年初发布了JRockit 1.0版本的第一个原型。尽管只是1.0版本(网上有人说它“非常1.0”,不够成熟),但是它应用于多线程服务器程序时性能优异,风光一时。以此为契机,我们获得了更多的投资,并将咨询业务拆分为一个独立的分公司,公司的名字也从Appeal Software Solutions变成了Appeal Virtual Machines。我们又雇用了一些销售人员,并就Java许可证的问题开始与Sun公司协商。

JRockit的相关工作越来越多。2001年,处理咨询业务的工程师都转入了与JVM相关的项目中,咨询公司宣告停业。这时,我们清楚地知道如何将JRockit的性能再提升一步,同时也意识到在这个过程中我们消耗资源的速度太快了。于是,管理层开始寻找合适的大公司,以实现整体收购。

2002年2月,BEA公司收购Appeal Virtual Machines公司,这让投资人松了一口气,同时也保证了我们有足够的资源做进一步的研究和开发。为了配合测试,BEA建立了一个宽敞的服务器机房,加固了地板,保证了电力供应。那时,有一根电缆从街上的接线盒通过服务器机房的窗户连进来。过了一段时间,这个服务器机房已经无法放下开发测试所需的全部服务器了,于是我们又租了一个机房来放置服务器。

作为BEA平台的一部分,JRockit的发展相当理想。在BEA的前两年,我们为JRockit开发了很多区别于其他Java解决方案的新特性,例如后来发展成为JRockit Mission Control的开发框架。此后,新闻发布、世界级的测试跑分和虚拟化平台随之而来。在拥有了JRockit后,BEA与Sun、IBM并列为三大JVM厂商,成为了拥有数千用户的平台。JRockit产生的利润,首先是来自工具套件,然后是产品JRockit Real Time提供的无比强大的GC性能。

2008年,Oracle收购BEA,这一事件起初令人感到不安,但是JRockit和相关团队最终获得了更多的关注和赞誉。

经过这些年的发展,令我们引以为荣的是,JRockit的用户遍布全球,它为关键应用的稳定运行保驾护航。同样令我们感到骄傲的是,当初6个少年在斯德哥尔摩老城区的一个小破屋中的设计已经成长为世界级产品。

本书的内容是我们十多年来与自适应运行时,尤其是JRockit,打交道的经验总结。据我们所知,其中的很多内容之前还没有发表过。

希望本书能对你有所帮助和启发。内容概述

第1章:起步。这一章对JRockit JVM和JRockit Mission Control做了简要介绍,内容包括如何获得相关软件及软件对各平台的支持情况,在切换JVM厂商的产品时需要注意的问题,JRockit和JRockit Mission Control版本号的命名规则,以及如何获取更多有关JRockit JVM的内容。

第2章:自适应代码生成。这一章对自适应运行时环境中的代码生成做了简要介绍。具体说来,解释了为什么在JVM中实现自适应代码生成比在静态环境中更有难度,而其实现所能发挥的效用却更加强大;介绍了赌博式的性能优化技术;通过一个例子介绍了JRockit的代码生成和优化流水线;讨论了自适应代码优化和传统代码优化;介绍了如何使用标志和指令文件来控制JRockit的代码生成。

第3章:自适应内存管理。这一章对自适应运行时环境中的内存管理做了介绍。通过介绍自动内存管理的相关概念和算法,解释了垃圾回收器的工作机制。详细介绍了JVM在为对象分配内存时所做的具体工作,以及为便于执行垃圾回收所需记录的元数据信息。后半部分主要介绍用于控制内存管理的最重要的Java API,以及可在Java应用程序中生成确定性延迟的JRockit Real Time产品。最后,介绍了如何使用标志来控制JRockit JVM的内存管理系统。

第4章:线程与同步。这一章介绍了Java和JVM中非常重要的线程与同步的概念及其在JVM中的简要实现,并深入讨论了Java内存模型及其内在的复杂性。简单介绍了基于运行时信息反馈的自适应优化对线程和同步机制的实现的影响。此外,还以双检查锁失效为例对多线程编程中常见的一些错误做了介绍。最后讲解了如何分析JRockit中的锁,以及如何通过标志控制线程的部分行为。

第5章:基准测试与性能调优。这一章讨论了基准测试的相关性,以及制定性能目标和指标的重要性;阐释了如何针对特定问题设计适合的基准测试;介绍了一些针对Java的工业级基准测试套件;详细讨论了如何根据基准测试的结果优化应用程序和JVM;以JRockit JVM为介绍了相关命令行参数的使用。

第6章:JRockit Mission Control套件。这一章介绍了JRockit Mission Control工具套件,包括启动和各种详细配置等内容。解释了如何在Eclipse中运行JRockit Mission Control,以及如何配置JRockit以使Eclipse在JRockit上运行。介绍了几种不同的工具,统一了相关术语的使用。讲解了如何使用JRockit Mission Control远程访问JRockit JVM,以及与故障处理相关的内容。

第7章:Management Console。这一章介绍了JRockit Mission Control中的Management Console组件,讲解了诊断命令的概念以及如何在线监控JVM实例,还介绍了触发器规则的设置和事件通知的机制,最后讲解了如何利用自定义组件扩展Management Console。

第8章:JRockit Runtime Analyzer。这一章介绍了JRockit运行时分析器(JRockit Runtime Analyzer,JRA),它是一款可以定制的按需分析框架,用于详细记录JRockit以及运行在其中的应用程序的执行状况,以便进行离线分析。其记录内容包括方法和锁的性能分析、垃圾回收信息、优化决策信息、对象统计信息以及延迟事件等。这一章最后介绍了如何根据这些记录信息来判别常见问题以及如何延迟分析。

第9章:JRockit Flight Recorder。这一章详细介绍了JFR(JRockit Flight Recorder)。新版本JRockit Mission Control套件使用JFR取代了JRA。这一章讲解了JFR与JRA的区别,最后介绍了如何扩展JFR。

第10章:Memory Leak Detector。这一章介绍了JRockit Mission Control套件中的最后一个工具JRockit Memory Leak Detector。其中介绍了具有垃圾回收功能的编程语言中内存泄漏的概念,以及Memory Leak Detector的一些用例。Memory Leak Detector不仅可以用来找出Java应用程序中意外持有的对象,还可以对Java堆做通用分析。此外还介绍了Memory Leak Detector的一些内部实现机制,以及它能保持很低的运行开销的原因。

第11章:JRCMD。这一章介绍了命令行工具JRCMD。用户可以通过JRCMD与目标机器上的JVM交互,并发送诊断命令。这一章按字母表顺序列出了JRCMD中最重要的诊断命令,并通过示例讲解了如何使用这些命令来检测或修改JRockit JVM的状态。

第12章:JRockit Management API。这一章介绍了如何编程实现对JRockit JVM内部功能的访问,如JRockit Mission Control套件就是基于Management API来实现的。尽管这一章介绍的JMAPI和JMXMAPI并未得到完整的官方支持,但从中可以了解到一些JVM的工作机制。希望读者可以实际动手操作一下,以加深理解。

第13章:JRockit Virtual Edition。这一章介绍了现代云环境中的虚拟化,其中包括了JRockit Virtual Edition产品的相关概念和具体细节。通常来说,操作系统很重要,但对于JRockit Virtual Edition来说,移除软件栈中的操作系统层并不是什么大问题,而且移除之后还可以降低操作系统层所带来的性能开销,降低的程度甚至在物理硬件上也达不到。阅读前提

请正确安装JRockit JVM和运行时环境。为了更好地理解本书的内容,请使用JRockit R28或其之后的版本,不过使用JRockit R27也是可以的。此外,正确安装Eclipse for RCP/Plug-in Developer也很有必要,尤其是尝试用不同的方法扩展JRockit Mission Control以及使用源码包中的程序时。目标读者

本书主要面向以Java为工作中心,并已具备一定知识技能的人员,例如对Java开发或安装管理有相关工作经验的开发人员或系统管理员。书中内容分为3大部分。

第一部分着重介绍了JVM和自适应运行时的作用及工作原理,还指出了自适应运行时以及JRockit的优势和劣势,以便在适当的时候解释什么是良好的Java编码实践。深入到JVM这个黑盒中,探查运行Java应用程序时到底发生了什么。理解第一部分的内容可以帮助开发人员和架构师理解某些设计决策的后果,进而做出更好的决策。这部分也可作为高校自适应运行时课程的学习资料。

第二部分着重介绍了JRockit Mission Control套件的具体功能,以及如何使用它来查找应用程序的性能瓶颈。对于想要对JRockit系统做性能调优以运行特定程序的系统管理员和开发人员来说,这部分内容非常有用。对于希望优化Java应用程序以提高资源利用率、优化性能的开发人员来说,这部分内容也很有用。但应该记住的是,对JVM层面的调优也只有这么多了,对应用程序本身的业务逻辑和具体实现做调优其实是更简单、更有效的。本书将会介绍如何使用JRockit Mission Control 套件来查找应用程序的瓶颈,以及如何控制硬件和程序运行的成本。

第三部分介绍了新近和即将发布的重要的JRockit相关技术,主要面向对Java技术发展方向比较感兴趣的读者。这部分内容着重讲解了Java虚拟化。

最后,列出了本书的参考文献和术语表。排版约定

本书中会包含一些代码,包括Java代码、命令行和伪代码等。Java代码以等宽字体表示,并按照标准Java格式显示。命令行和参数也会以等宽字体显示。类似地,段落中引用的文件名、代码片段和Java包名也会使用等宽字体表示。

与正文相关的重要一些信息,或是补充说明,会使用中括号括起来。 此部分内容很重要!

技术名词和基本概念会作为关键字用黑体字表示。为便于查询,这些技术名词会列在术语表中。

在本书中,JROCKIT_HOME和JAVA_HOME表示JRockit JDK/JRE的安装目录。例如,默认安装JRockit之后,Java命令的位置是:

C:\jrockits\jrockit-jdk1.5.0_17\bin\java.exe

而JROCKIT_HOME和JAVA_HOME的值则为:

C:\jrockits\jrockit-jdk1.5.0_17\

JRockit JVM有其自己的版本号规则,目前最新的主版本是R28。JRockit的次版本号表示在发行主版本后第几次发行小版本,例如R27.1和R27.2。本书中使用R27.x表示所有的R27版本,R28.x表示所有的R28版本。 默认情况下,本书所介绍的内容是以R28版本为基础的,针对之前版本的内容会特别说明。

JRockit Mission Control客户端使用了更加标准的版本号规则,例如4.0。在介绍JRockit Mission Control的相关工具时,工具的版本号3.x和4.0也分别对应了JRockit Mission Control客户端的版本。在写作本书时,JRockit Mission Control客户端的最新版本是4.0,除非特别指明,所有内容均是以此版本为基础来讲解的。

书中内容有时会涉及一些第三方产品。阅读本书时无须十分了解这些产品。涉及的第三方产品如下。● Oracle WebLogic Server:Oracle J2EE应用服务器● Oracle Coherence:Oracle内存型分布式缓存技术● Oracle Enterprise Manager:Oracle应用程序管理套件● Eclipse:Java IDE(也可用于其他语言的开发)● HotSpot™:HotSpot™JVM读者反馈

欢迎读者反馈对本书的看法,喜欢什么、不喜欢什么,这对我们开发读者真正需要的选题来说非常重要。

要发送反馈信息,可以直接发邮件到feedback@packtpub.com,并在邮件主题中注明书名。

如果你需要某本书,希望我们出版的话,请在PacktPub的官网www.packtpub.com中填写表单,或者发邮件到suggest@packtpub.com来说明。

如果读者精通某个领域,并且想要撰写或参与写作一本书的话,请阅读www.packtpub.com/authors中的作者指南。客户支持

针对购买了Packt图书的读者,我们提供了很多周边内容,帮助你更好地理解书中内容。 下载本书示例代码可从http://www.ituring.com.cn/book/2491下载本书中的示例代码,以及代码的使用说明。勘误

尽管我们尽力确保书中内容无误,但错误在所难免。如果读者发现了错误,不管是文字错误还是代码错误,敬请告知,我们将感激不尽。这不仅可使其他读者免受错误困扰,还可以帮助我们完善本书后续的版本。如果读者发现了任何错误,请访问http://www.packtpub.com/support,选择书名,然后点击let us know链接,1输入勘误的具体内容。当勘误通过验证后,内容将被接受,而且该勘误信息将上传到我们的网站,或者添加到该书下面Errata部分的已有勘误表列表当中。在http://www.packtpub.com/support可以看到目前已有的勘误表。1针对本书中文版的勘误,请到http://www.ituring.com.cn/book/2491查看和提交。——编者注盗版问题

对所有媒体来说,互联网盗版都是一个长期存在的问题。Packt公司对自己的版权和许可证的保护非常严格。如果你在互联网上遇到以任何形式非法复制我们作品的行为,请立刻向我们提供具体地址或网站名称,以帮助我们采取补救措施。

请通过copyright@packtpub.com联系我们,并且附上可疑盗版资料的链接。

感谢你帮助我们保护作者,使我们能够带给你更有价值的内容。疑问

如果读者对本书有任何疑问,请发邮件至questions@packtpub.com说明,我们会尽力解决。致谢

感谢这些年一直陪伴在我们身边的富有创造力的人们。特别是Appeal的同事,你们已经成为我们生活的一部分,我们很荣幸能与如此卓越的团队分享这段历程。

此外,非常感谢我们的家人,感谢你们在本书写作期间给予我们的耐心和支持。  第1章 起步

虽然本书各个部分(主要是第一部分)包含了关于所有自适应运行时环境的内部工作原理的一般性信息,但示例和深入的信息都是针对JRockit JVM的。本章简要介绍了如何获取JRockit JVM,以及将Java应用部署到JRockit JVM上时可能遇到的问题等。

在本章中,你将学到如下内容:● 如何获取JRockit● JRockit对各平台的支持情况● 如何将应用迁移到JRockit● JRockit命令行选项简介● JRockit版本号命名规则● 遇到问题时该如何求助1.1 获取JRockit JVM

为了更好地理解本书的内容,推荐使用最新版本的JRockit JVM作为实验工具。对于JRockit R27.5之前的版本来说,要想使用某些高级特性是需要获取相关授权的。在Oracle收购了BEA公司后,对这些高级特性的限制得以解除,现在可以随时使用,无须担心授权问题,这使得开发人员可以在开发环境中更好地研究和使用JRockit JVM。当然,如果想在生产环境中使用JRockit,仍然是需要购买授权的。对于Oracle的客户来说,这不是什么问题,因为Oracle的大部分应用套件都包含了JRockit JVM,例如所有包含了WebLogic Server的套件都包含JRockit JVM。

在编写本书之时,获取JRockit JVM的最简单方式就是下载并安装JRockit Mission Control(一个用于诊断和剖析JRockit JVM的工具套件)。JRockit Mission Control发行版的安装目录几乎与JDK的安装目录相同,可以当作JDK使用。我们很希望能够为JRockit提供一个自包含的、只有JVM的JDK,不过暂时未能实现,希望在不远的将来能有所改观。

在下载JRockit Mission Control之前,请先确认你所使用的平台是在支持范围之内的,凡是支持JRockit的平台,也都支持JRockit Mission Control的服务端组件。

下表展示了JRockit Mission Control 3.1.x在各个平台的支持情况。平台Java 1.4.2Java 5.0Java 6×××Linux x86Linux ××N/Ax86-64Linux ×(仅服务×(仅服务N/AItanium器端)器端)Solaris ×(仅服务×(仅服务×(仅服务SPARC(64器端)器端)器端)位)Windows ×××x86Windows ×(仅服务×(仅服务N/Ax86-64器端)器端)Windows ×(仅服务×(仅服务N/AItanium器端)器端)

下表展示了JRockit Mission Control 4.0.0在各个平台的支持情况。平台Java 5.0Java 6Linux x86××××Linux x86-64Solaris ×(仅服务器×(仅服务器SPARC(64位)端)端)Windows x86××Windows ×(仅服务器×(仅服务器x86-64端)端) 如果在Windows平台上运行JRockit Mission Control,请确保操作系统的临时目录所在的文件系统可以针对每个用户单独设置文件访问权限。换句话说,临时目录不要设置在FAT格式的磁盘上,否则,某些关键特性(例如本地JVM的自动检测)将无法使用。1.2 将应用程序迁移到JRockit

本书中,JRockit JVM的安装目录以JROCKIT_HOME指代,将之设为系统变量可以使操作更简便。安装完成后,顺便将JROCKIT_HOME/bin目录添加到系统环境变量PATH路径中,并更新应该迁移到JRockit的Java应用程序的脚本。建议读者将环境变量JAVA_HOME的值设置为JROCKIT_HOME指代的目录。大部分情况下,JRockit都可以直接替代其他JVM,但某些启动参数需要调整,例如某些控制具体垃圾回收行为的参数,这在不同JVM厂商之间有较大差别。其他一些比较通用的参数,例如设置堆大小的最大值,在设置的时候是相同的。 更多有关将应用程序迁移到JRockit JVM的信息,请参见JRockit在线文档中“Migrating Applications to the Oracle JRockit JDK”一章的内容。1.2.1 命令行选项

在JRockit JVM中,主要有3类命令行选项,分别是系统属性、标准选项(以-X开头)和非标准选项(以-XX开头)。

系统属性

设置JVM启动参数的方式有多种。以-D开头的参数会作为系统属性使用,这些属性可以为Java类库(如RMI等)提供相关的配置信息。例如,在启动的时候,如果设置了-Dcom.jrockit.mc.debug=true参数,则JRockit Mission Control会打印出调试信息。不过,R28之后的JRockit JVM版本废弃了很多之前使用过的系统属性,转而采用非标准选项和类似HotSpot中虚拟机标志(VM flag)的方式设置相关选项。

标准选项

以-X开头的选项是大部分JVM厂商都支持的通用设置。例如,用于设置堆大小最大值的选项-Xmx在包括JRockit在内的大部分JVM中都是相同的。当然,也存在例外,如JRockit中的选项-Xverbose会打印出可选的子模块日志信息,而在HotSpot中,类似的(但实际上有更多的限制)选项是-verbose。

非标准选项

以-XX开头的命令行选项是各个JVM厂商自己定制的。这些选项可能会在将来的某个版本中被废弃或修改。如果JVM的参数配置中包含了以-XX开头的命令行选项,则在将Java应用程序从一种JVM迁移到另一种时,应该在启动JVM之前去除这些非标准选项。

确定了新的JVM选项后才可以启动Java应用程序。通常,Java应用程序迁移到JRockit JVM后,内存消耗会有些许增加,但能够获得更好的性能。

应该通过查询目标JVM的文档来确定要使用的非标准命令行选项是否在不同JVM厂商之间和不同JVM版本之间具有相同的语义。

虚拟机标志

JRockit R28之后的版本,添加了被称为虚拟机标志的命令行选项,作为非标准命令行选项的子集使用,其语法是:-XX:=。使用命令行工具JRCMD可以读取这些虚拟机标志的值,并且可以修改某些VM标志的值。更多关于JRCMD的内容,请参见第11章。1.2.2 行为差异

不同的JVM可能会有不同的运行时行为,通常这是因为不同的JVM对Java语言规范和JVM规范有不同的实现。请注意,各个JVM的行为虽有不同,但都是正确的。规范中的很多地方没有做强制规定,为JVM厂商实现独具特色的功能留出了余地。如果某个应用程序严重依赖于规范的某种具体实现,那么迁移该应用程序到其他实现时恐怕就不那么容易了。

例如,曾经在旧版本Eclipse上测试JRcokit的里程碑版本时,某些测试用例无法在JRockit上启动。后来发现,由于这些测试之间具有依赖性,测试需要以特定的顺序进行,而JRockit中通过反射获取的方法列表(Class#getDeclaredMethods)的实现与其他JVM不同,导致返回方法列表的顺序有所区别,尽管这并不违反规范,却导致了测试无法正常进行。后来,Eclipse的开发团队确认了“依赖方法列表的顺序”是错误的。最终测试用例得以正确运行。

如果在编写应用程序时不遵守Java语言规范或JVM规范,而是以某款JVM的特殊行为作为依据,那么将来该应用程序可能无法在同一个JVM厂商的新版本JVM中正常运行。如果在编写应用程序时遇到相关问题,请查阅Java语言规范和JDK相关文档。

将应用程序从一款JVM迁移到另一款JVM时,要特别注意两款JVM之间的区别。在原先的JVM上能够正常运行的应用程序,可能由于一些潜在的问题而无法在新的JVM上继续运行,例如应用程序在不同的JVM上具有不同的性能表现。这可能会导致一些问题的发生,但不应将之归咎于JVM。

例如,曾有客户报告说,JRockit只运行了一天就崩溃了。经过调查发现,该客户的应用程序在其他JVM上运行时,也会发生崩溃,只不过多运行了几天而已。之所以JRockit崩溃得更快,是因为该应用程序在JRockit上运行得更快,内存泄漏的速度更快而已。

当然,所有的JVM,包括JRockit,都可能存在bug。为了自称是Java,每个JVM实现都必须使用Java Compatibility Kit(JCK)进行大量的兼容性测试。

JRockit一直是使用分布式测试系统进行各种测试的。这个大测试套件包含了JCK,通过这个测试可以保证发布的JRockit是一款稳定、合格、兼容Java的JVM。在发行新版本的JRockit之前,会使用该测试套件,在JRockit上运行各种知名应用程序,例如Eclipse、WebLogic Server,以及专门设计用来进行压力测试的程序,这些测试会在所有受支持的平台上进行,以测试JRockit是否会发生故障。此外,对性能做持续的回归测试也是JRockit QA工作的重中之重。但即便如此,故障仍然无法避免。如果JRockit崩溃了,请将详细情况报告给Oracle的支持工程师。1.3 JRockit版本号的命名规则

JRockit版本号的命名规则有点复杂,至少包含以下3部分:

(1) JRockit JVM版本号

(2) JDK版本号

(3) JRockit Mission Control版本号

查看JVM版本号的方法是在命令行中执行命令java -version,典型的输出如下所示。java version "1.6.0_14" Java(TM) SE Runtime Environment (build 1.6.0_14-b08) Oracle JRockit(R) (build R28.0.0-582-123273-1.6.0_ 14-20091029-2121-windows-ia32, compiled mode)

JRockit版本号的第一个部分是与JVM绑定的JDK的版本号。该JDK版本与标准JDK版本同步,也就是与随HotSpot发行的JDK版本相同。从上面的例子可以看到,Java的版本是1.6,更新版本号是14-b08。如果你想看某个JDK发行版中修复了哪些安全问题,就可以直接查看这个版本号下对应的发行信息。

JRockit版本号以字母R开头。在上面的例子中,JRockit的版本号是R28.0.0。每个版本的JRockit JVM都可以支持多个版本的JDK。例如,R27.6.5支持Java 1.4、Java 1.5和Java 1.6,而在JRockitR28中,JDK 1.4已经不在支持范围内了。

紧跟JRockit JVM版本号的是构建号,然后是修改号。在上面的例子中,构建号是582,修改号是123273,Java版本号是1.6.0_14,在这之后的两个数字是构建的日期(使用ISO 8601格式)和时间(CET,欧洲中部时间),再后面是操作系统和CPU架构信息。

在命令行中执行命令jrmc -version或jrmc -version | more可以查看JRockit Mission Control的版本号。1.4 获取帮助

Oracle Technology Network中有很多关于JRockit和JRockit Mission Control的有用资源,例如博客、文章和论坛。JRockit开发人员和支持人员始终关注论坛内容,即使读者无法在已有的内容中找到所需的答案,相关人员也会在提出问题的几天之内做出回复。如果某些问题经常提及,就会被置顶,例如如何获取老版本JRockit的授权文件。1.5 小结

本章简要介绍了JRockit JVM,包括如何安装JRockit,以及在将Java应用程序从其他JVM迁移到JRockit JVM时应该注意的事情。

此外,还介绍了JRockit JVM支持的几种不同的命令行参数,并举例说明了如何查看JRockit JDK的版本号中的相关信息。

最后,提供了一些学习和使用JRockit时可能会用到的帮助信息。  第2章 自适应代码生成

本章介绍JVM运行时环境中的代码生成和代码优化,既有通用概念,也有JRockit代码生成的内部机制。首先介绍Java字节码格式和JIT编译器的运行机制,并举例说明自适应运行时所能发挥的威力。然后深入JRockit JVM,详细介绍代码生成的内容。最后介绍如何控制JRockit中的代码生成和优化。

在本章,你将学到如下内容:● 平台独立性的语言(如Java)的好处● Java字节码格式和JVM规范的关键信息● JVM如何解释执行字节码以执行Java程序● 自适应环境中的优化与静态预先编译的对比,前者效果好但难于

实现的原因,以及“性能赌博”的具体含义● 为什么自适应运行时中的代码生成能发挥更大的威力● 如何将Java编译为本地代码,面临的主要问题是什么。哪些优化

是应该由Java程序员做的,哪些是由JVM做的,或者哪些优化是

字节码级的● JRockit代码流水线的工作原理和设计初衷● 如何控制JRockit中的代码生成器2.1 平台无关性

Java诞生之初的一大卖点,也是使其成为主流编程语言的关键点,就是“一次编写,到处运行”的理念。Java编译器会将源代码编译为平台无关的、压缩的Java字节码,即.class文件。运行的时候,无须针对不同的硬件架构重新编译Java应用程序,因为运行Java程序的JVM是平台相关的,最终由它负责将字节码转换为本地代码。

这种运行方式大大提升了程序的可移植性,而使用其他一些编程语言(如C++)编写的应用程序会被直接编译为平台相关的格式,极大地降低了灵活性。以x86架构为例,C++编译器可能会对程序做大量的针对x86平台的优化,所以编译后的应用程序也只能在x86架构的平台上运行,无法直接移植到SPARC平台上,必须要重新编译,而且可能还不得不使用一款优化能力弱于x86平台的编译器才行。此外,如果x86平台本身做了升级,添加了新的指令,那么已经编译过的程序就无法利用这些新的特性,除非是重新编译一遍。当然,可以通过只发行源代码的方式来间接达到可移植性的要求,但可能又会受到各种授权的限制。而对Java来说,可移植性问题交由JVM解决,程序员无须为此操心。

就Java来说,凡是安装有JVM的平台都可以运行Java程序。具有平台独立特性的字节码并非由Java发明,而且之前已经应用于其他几种编程语言,例如Pascal和Smalltalk,但Java是第一个将之作为一大卖点推广的。

Java刚刚出现时,使用Java编写的应用程序大多是Applet形式的小程序,用于嵌入到Web浏览器中运行,是典型的客户端应用程序。然而,跨平台并非Java的唯一亮点,它还包括其他一些令人兴奋的特性,例如内建的内存管理、缓冲越界保护和安全沙箱模型等。这些特性使Java不仅可以用于客户端程序的开发,还可以满足服务器端复杂业务逻辑的开发要求。

经过几年的努力发展,Java在服务器端开发的能力终于得到了广泛认可,其固有的健壮性使其开发速度快于C++,因此得以在服务器端开发中广为采用。当应用程序逻辑很复杂时,更短的开发周期就显得尤为重要,这一点在服务器端开发领域更受关注。2.2 Java虚拟机

尽管使用平台无关的字节码可以完全满足不同平台对可移植性的要求,但实际上,CPU本身并不能直接执行字节码指令,它只认识本地代码。 在本书中,专用于某个硬件架构的代码称为本地代码(native code)。例如,对于x86平台来说,x86汇编语言和x86机器代码即为本地代码。机器代码是二进制的平台相关的代码,而汇编语言则是以人类可读的形式表示的机器代码。

因此,JVM需要将字节码转化为匹配当前硬件架构的本地代码供CPU执行。具体有以下两种实现方式(也可能会综合使用这两种方式)。● JVM规范将JVM描述为一个状态机器,因此实际上并不需要真的

将字节码转化为本地代码执行。JVM可以完整模拟Java程序的执

行状态,例如可以将每条字节码模拟为一个JVM状态函数。这种

方式称为字节码解释执行(bytecode interpretation),在这种情

况下,唯一直接执行的本地代码(这里暂不考虑JNI)就是JVM

本身。● JVM将字节码编译为匹配目标平台的本地代码,然后再调用执行

这些本地代码。一般情况下,将字节码编译为本地代码这一步发

生在某个方法第一次被调用的时候。这个过程就是众所周知的即

时编译(Just-In-Time compilation,也叫JIT编译)。

自然地,将字节码编译为本地代码后,程序的执行效率会比解释执行快几个数量级,不过,这是以额外的信息记录和编译时间为代价的。2.2.1 基于栈的虚拟机

JVM是一种基于栈的虚拟机,绝大部分字节码操作都是处理操作数栈的内容,从栈中弹出内容,计算,再将结果放回栈中。例如,执行求和操作时,会将两个操作数入栈,执行加法指令,它会使用到这两个操作数,然后将加法的结果入栈,使用结果的时候再将操作结果出栈。

除了操作数栈之外,按照字节码的格式,还有多达65 536个寄存器可以使用。寄存器也称为局部变量(local variable)。

在字节码格式中,操作指令都被编码在一个字节中,也就是说,Java最多支持256种操作码(opcode),每种操作都对应着一个唯一值和类似于汇编指令的助记符(mnemonic)。 长久以来,JVM规范中只增加了一个新的操作码,即0xba,这个值是为了将来提供对invokedynamic操作的支持而预留的。该操作用于解决将动态语言(例如Ruby)编译为字节码时遇到的动态分派(dynamic dispatch)问题。更多有关将字节码应用于动态语言的内容,请参见Java Specification Request (JSR) 292的描述。2.2.2 字节码格式

下面的代码展示了名为add的方法及其编译后的字节码格式:public int add(int a, int b) { return a + b;}public int add(int, int); Code: 0: iload_1 // stack: a 1: iload_2 // stack: a, b 2: iadd // stack: (a+b) 3: ireturn // stack:}

函数add有两个输入参数a和b,分别被放入局部变量槽1和局部变量槽2中(在这个例子中,方法add是一个实例方法。根据JVM规范,实例方法局部变量槽0中存放的是this)。前两个操作,即iload_1和iload_2,用于将局部变量槽1和局部变量槽2中的值放入到操作数栈中。第三个操作iadd从操作数栈中弹出两个数,对其求和,并将结果入栈。第四个操作ireturn弹出之前计算出的和,以该值作为返回值,方法结束。上面例子中的每一步字节码操作旁边都有关于操作数栈操作的注释,读者可自行揣摩。 使用JDK附带的命令行工具javap可以对字节码进行反汇编。

操作与操作数

JVM字节码是一种非常紧凑的格式,前面例子中的方法的字节码表示只用了4字节(源代码的一小部分)。每种操作都使用一个字节表示,后跟一个可选的、长度可变的操作数。一般情况下,带有操作数的字节码指令的长度不会超过3字节。

下面的代码是判断一个数是否为偶数的函数,及其编译为字节码后的样子。字节码使用十六进制的数字加以标注,分别表示字节码的操作码和操作数的值:public boolean even(int number) { return (number & 1) == 0;}public boolean even(int); Code: 0: iload_1 // 0x1b number 1: iconst_1 // 0x04 number, 1 2: iand // 0x7e (number & 1) 3: ifne 10 // 0x9a 0x00 0x07 6: iconst_1 // 0x03 1 7: goto 11 // 0xa7 0x00 0x04 10: iconst_0 // 0x03 0 11: ireturn // 0xac}

在上面的代码中,首先将传入的参数number和常量1压入到操作数栈中,然后将它们都弹出求和,即执行iand指令,并将结果压入操作数栈。指令ifne进行条件判断,从操作数栈中弹出一个操作数做比较判断,如果不是0的话,就跳转到其他分支运行。指令iconst_0将常量0压入到操作数栈中,其操作码为0x03,无须后跟操作数。类似地,指令iconst_1会将常量1压入操作数栈中。返回值为布尔类型时是使用常量整数来表示的。

比较和跳转指令,例如ifne(如果不相等则跳转,字节码是0x9a),通常需要使用两个字节的操作数(以满足16位跳转偏移的要求)。 举个例子,假如某条指令是,若条件判断的值为true,则将指令指针向前移动10 000字节的话,那么这个操作的编码应该是0x9a 0x27 0x10(注意,0x2710是10 000的十六进制表示。字节码中数字的存储是大端序的)。

字节码中还包含其他一些复杂结构,例如分支跳转,是通过在tableswitch指令后附加包含了所有跳转偏移的分支跳转表实现的。

常量池

程序,包含数据和代码两部分,其中数据作为操作数使用。对于字节码程序来说,如果操作数非常小或者很常用(如常量0),则这些操作数是直接内嵌在字节码指令中的。

较大块的数据,例如常量字符串或比较大的数字,是存储在.class文件开始部分的常量池(constant pool)中的。当使用这类数据作为操作数时,使用的是常量池中数据的索引位置,而不是实际数据本身。以字符串数据aVeryLongFunctionName为例,如果在编译方法时每次都要重新编码这个字符串的话,那字节码就谈不上压缩存储了。

此外,Java程序中的方法、属性和类的元数据等也作为.class文件的组成部分,存储在常量池中。2.3 代码生成策略

JVM执行字节码指令时有几种不同的方式,如以字节码解释器来模拟字节码的执行,以及将全部代码编译为匹配某个平台的本地代码再执行。2.3.1 纯解释执行

早期的JVM使用解释器来模拟字节码指令的执行。为了简化实现,解释器就是在一个主函数中加上一个包含了所有操作码的分支跳转结构。调用该函数时,会附带上表示操作数栈和局部变量的数据结构,以此作为字节码操作的输入输出。总体来看,解释器的核心代码最多也就几千行。

纯解释执行这种方式简单有效,如果想要添加对新硬件架构的支持,只需简单修改代码,重新编译即可,无须编写新的本地编译器。而且写一个本地编译器的代码量也比写一个使用分支跳转结果的纯解释器大得多。

解释器在执行字节码时几乎不需要记录额外的信息,而编译执行的JVM会将一些或全部字节码编译为本地代码,这时就需要跟踪所有经过编译的代码。如果某个方法在应用程序运行过程中发生了改变(Java里可以这么做),就需要重新生成代码。相比之下,解释器只需要在下次模拟调用时再解释一遍新的字节码就可以了。

因为解释执行所需要记录的额外信息极少,所以就很适用于像JVM这样在运行过程中随时可能改变代码的自适应运行时。

当然,相比于执行编译为本地代码的方式,纯解释执行的性能很差。Sun公司的Classic Virtual Machine起初就是使用纯解释执行的方式。

之前的示例代码中,编译后的方法有4个字节码指令,使用C语言编写解释器来运行的话,可能需要多达10倍的本地指令才能完成。相比之下,编译为本地代码的add方法最多只需要两条汇编指令就足够了,即add和return。int evaluate(int opcode, int* stack, int* localvars) { switch (opcode) { ... case iload_1: case iload_2: int lslot = opcode - iload_1; stack[sp++] = localvars[lslot]; break; case iadd: int sum = stack[--sp] + stack[--sp]; stack[sp++] = sum; break; case ireturn: return stack[--sp]; ... }}

上面的示例代码以伪代码展示解释器如何执行add方法,从中可以看到,即使是如此简单的add方法,在JVM中运行时,也需要数十条汇编指令才能完成,而编译为本地代码后只需要两条汇编指令,这就是纯解释执行性能差的原因。

在x86平台上,经过JIT编译的add会生成如下代码:add eax, edx // eax = edx+eaxret // return eax 有时为了更好地阐述观点,书中会贴出一些汇编代码,不要担心,即使读者之前没有学习过汇编语言,也能够明白其中的含义。但是,为了更好地理解本书的内容,读者最好了解一些低级语言的基本概念。如果你实在无法理解本书中列出的汇编语言,也不必担心,这并不影响你理解本书的核心内容。2.3.2 静态编译

Java诞生的初期,为了避免解释执行字节码所带来的性能问题,程序员迫不得已使用了一些“简单粗暴”的方法,即静态编译。那时,Java程序在运行之前,会被直接编译为本地代码,这种方式称为预编译(ahead-of-time compilation)。其实,就是把Java当C++用。

随着Java中静态编译的完善,20世纪90年代后期市场上出现了不少这样的产品,它们将字节码转换为C语言代码,再由C语言编译器将之编译为本地代码。大部分情况下,静态编译生成的代码的执行效率比纯解释执行高得多。不过,这种方式却抛弃了Java语言的动态特性,也无法妥善应对在运行过程中代码发生变化的情况。

静态编译的一大劣势就是抛弃了Java语言平台独立的特性。在这里,JVM已经被无视了。

静态编译所带来的另一个问题是,Java中的内存本来是自动管理的,现在却或多或少需要手动执行管理操作,严重影响伸缩性。

随着Java语言的动态特性受到广泛关注,其在服务器端发挥的作用越来越大,静态编译模式也变得越来越不实用。例如,服务器端应用程序可能会在运行过程中产生大量的JSP(Java server page),实际上是把静态编译器作为JIT编译器使用,运行起来会慢一些,自适应特性也会差一些。 尽管静态预编译这个方案并不适合于实现Java,但可以用在其他地方,例如预分析(ahead-of-time analysis)。程序分析是很耗费时间的,如果能够在程序运行之前,在离线环境下完成部分程序分析工作,并与JVM就这部分信息交互,可以使程序运行得更好。例如,将性能分析数据以注解的形式存储到.class文件中,可以帮助JVM更好地运行目标应用程序。2.3.3 完全JIT编译

另一种加速字节码执行速度的方法是彻底抛弃解释器,当首次调用某个Java方法时,将其编译为本地代码。这种编译方式是发生在运行时的,在JVM内部完成,因此不属于预编译范畴。

与静态预编译不同,在运行时编译更适合具有动态特性的Java编程语言。

完全JIT编译的好处是不需要维护解释器,但缺点是编译时间影响主体业务程序的运行。编译器对所有方法一视同仁,在编译那些热方法的同时,也会编译那些执行次数较少的,甚至只执行一次的方法。实际上,这些方法本可以解释执行的。 经常调用的方法称为热方法(hot method),而那些不经常调用的、对程序的整体性能没什么影响的方法则称为冷方法(cold method)。

上面提到的问题,可以通过在JIT编译器中添加不同层级的编译操作来修正。例如,在首次调用某个方法的时候,先提供一个编译快速的但优化得不太完善的版本。当JVM探查到某个方法是热方法时,例如对该方法的调用次数超过了某个阈值,准备重新编译这个方法,这时就可以使用一些复杂的优化方法了。当然,这种方式花费在编译上的时间更多。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载