实战Java虚拟机———JVM故障诊断与性能优化 (第2版)(txt+pdf+epub+mobi电子书下载)


发布时间:2020-09-07 14:47:43

点击下载

作者:葛一鸣

出版社:电子工业出版社

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

实战Java虚拟机———JVM故障诊断与性能优化 (第2版)

实战Java虚拟机———JVM故障诊断与性能优化 (第2版)试读:

前言

关于Java生态圈

Java是目前应用最广泛的软件开发平台之一。随着Java及Java社区的不断壮大,Java也早已不再是简简单单的一门计算机语言了,它更是一个平台、一种文化、一个社区。

作为一个平台,Java虚拟机扮演着举足轻重的作用。除了Java语言,任何一种能够被编译成字节码的计算机语言都属于Java这个平台。Groovy、Scala、JRuby、Kotlin等都是Java平台的一部分,它们依赖于Java虚拟机,同时,Java平台也因为它们变得更加丰富多彩。

作为一种文化,Java几乎成了“开源”的代名词。在Java程序中,有着数不清的开源软件和框架,如Tomcat、Struts、Hibernate、Spring等。就连JDK和JVM自身也有不少开源的实现,如OpenJDK、Harmony。可以说,“共享”的精神在Java世界里体现得淋漓尽致。

作为一个社区,Java拥有无数的开发人员,有数不清的论坛和资料。从桌面应用软件、嵌入式开发到企业级应用、后台服务器、中间件,都可以看到Java的身影。其应用形式之复杂、参与人数之众多也令人咋舌。可以说,Java社区俨然已经成为一个良好而庞大的生态系统,而本书,将主要介绍这个生态系统的核心——Java虚拟机。

第2版的重点修订

作者希望本书第2版的内容能够涵盖JDK 7~JDK10,所以特做如下修订:

· JDK 10 源码环境的下载与搭建。

· JDK 10 JVM的调试方法介绍。

· JDK 10 运行参数的演变。

· JDK 10用G1垃圾回收器替代CMS垃圾回收器,成为默认的垃圾回收器。

· JDK 10调试工具的更新,废除功能类似的调试工具,重点推荐使用Visual VM。

本书的体系结构

本书立足于实际开发,又不缺乏理论介绍,力求通俗易懂、循序渐进。全书共分为11章。

第1章综述,介绍了Java虚拟机的概念、定义,讲解了Java语言规范和Java虚拟机规范,最后还介绍了OpenJDK的调试方法。

第2章介绍了Java虚拟机的总体架构,说明了堆、栈、方法区等内存空间的作用和彼此之间的联系。

第3章介绍了Java虚拟机的常用配置参数,重点对垃圾回收跟踪参数、内存配置参数做了详细介绍,并给出了案例说明。

第4章从理论层面介绍了垃圾回收的算法,如引用计数、标记清除、标记压缩、复制算法等。本章是第5章的理论基础。

第5章基于垃圾回收的理论知识,进一步详细介绍了Java虚拟机中实际使用的各种垃圾回收器,包括串行回收器、并行回收器、CMS、G1等。

第6章介绍了Java虚拟机的性能监控和故障诊断工具,考虑到实用性,也介绍了系统级性能监控工具的使用,两者结合,可以更好地帮助读者处理实际问题。

第7章详细介绍了对Java堆的分析方法和案例,主要讲解了MAT和Visual VM两款工具的使用,以及各自OQL的编写方式。

第8章介绍了Java虚拟机对多线程,尤其是对锁的支持。本章不仅介绍了虚拟机内部锁的实现、优化机制,也给出了Java语言层面的一些锁优化思路,最后还介绍了无锁的并行控制方法。

第9章介绍了Java虚拟机的核心——Class文件结构,Class文件作为Java虚拟机的基石,有着举足轻重的作用,对深入理解Java虚拟机有着不可忽视的作用。

第10章介绍了Java虚拟机中类的装载系统,其中着重介绍了Java虚拟机中ClassLoader的实现及设计模式。

第11章介绍了Java虚拟机的执行系统和字节码。为了帮助读者更快、更好地理解Java字节码,本章对字节码进行了分类讲解,并且理论联系实际,给出了通过ASM框架进行字节码注入的案例。

本书特色

本书的主要特点有:

· 结构清晰。本书采用从整体到局部的视角,首先,第1、2章介绍了Java虚拟机的整体概况和结构。接着步步为营,每一章节对应单独的知识点,力求展示虚拟机的全貌。

· 理论结合实战。本书不甘心于简单地枚举理论知识,在每一个理论背后,都给出了演示示例供读者参考,帮助读者更好地消化这些理论。比如,在对Class文件结构和字节码的介绍中,不仅仅简单地给出了理论说明,更是使用ASM框架将这些理论应用于实践,尽可能地做到理论与实践结合。

· 专注专业。本书着眼于Java虚拟机,对Java虚拟机的原理和实践做了丰富的介绍,包括但不限于体系结构、虚拟机的调试方式、常用参数、垃圾回收系统、Class文件结构、执行系统等,力求从多角度更专业地对Java虚拟机进行探讨。

· 通俗易懂。本书依然服务于广大虚拟机初学者,尽量避免采用过于理论化的描述方式,简单的白话文风格贯穿全书,尽量使读者在阅读过程中少盲点、无盲点。

· 技术全面。纵横Windows和Linux双系统下的性能诊断、涉及32位系统和64位系统的优化比较、贯穿从JDK 1.5到JDK 10的优化演进。

适合阅读人群

虽然本书讲解力求通俗,但要通读本书并取得良好的学习效果,要求读者需要具备基本的Java知识或者一定的编程经验。因此,本书适合以下读者:

· 拥有一定开发经验的Java平台开发人员(Java、Scala、JRuby等)。

· 软件设计师、架构师。

· 系统调优人员。

· 有一定的Java编程基础并希望进一步理解Java的程序员。

· 虚拟机爱好者,JVM实践者。

本书的约定

本书在叙述过程中,有如下约定:

· 本书中所述的JDK 1.5、JDK 1.6、JDK 1.7、JDK 1.8、JDK 1.9、JDK 1.10等同于JDK 5、JDK 6、JDK 7、JDK 8、JDK 9、JDK 10。

· 如无特殊说明,Java虚拟机均指HotSpot虚拟机。

· 如无特殊说明,本书的程序、示例均在JDK 7~JDK 10环境中运行。

联系作者

本书的写作过程远比想象的艰辛,为了让全书能够更清楚、更正确地表达和论述,我经历了好多个不眠之夜,即使现在回想起来,也忍不住会打个寒战。由于写作水平的限制,书中难免会有不妥之处,望读者谅解。

为此,如果读者有任何疑问或者建议,非常欢迎大家加入QQ群254693571,一起探讨学习中的困难、分享学习的经验,我期待与大家一起交流、共同进步。同时,也希望大家可以关注我的博客http://www.uucode.net/。

感谢

这本书能够面世,是因为得到了众人的支持。首先,要感谢我的妻子,她始终不辞辛劳,毫无怨言地对我照顾有加,才让我得以腾出大量时间,并可以安心工作。其次,要感谢编辑为我一次又一次地审稿改错、批评指正,才让本书逐步完善。最后,感谢我的母亲30年如一日对我的体贴和关心。

特别感谢网友千马奕在JDK 1.9、JDK1.10下对本书所做的测试和修订。第1章 初探Java虚拟机

什么是Java虚拟机?什么是Java语言?两者又有何关系?作为本书开篇之章,本章将主要介绍有关Java虚拟机的基本概念、发展历史和实现概要。其中,将重点介绍支撑Java世界的两份重要规范—Java语言规范和Java虚拟机规范,帮助读者更好地理解Java生态圈。

本章涉及的主要知识点有:

· 读懂Java的发展历史。

· 学习Java虚拟机的概念和种类。

· 接触Java语言规范。

· 了解Java虚拟机规范。

· 掌握单步调试Java虚拟机的方法。1.1 知根知底:追溯Java的发展历程

目前,Java语言可以说是最常用的编程语言之一,在应用软件领域,它唯一的竞争对手似乎只有微软的.NET。C/C++作为曾经的霸主,目前依然占据着系统软件和嵌入式系统绝对的市场份额,但正在逐步退出应用软件领域。和C/C++相比,Java在设计上有着绝对的优势,开发人员可以尽快从语言本身的复杂性中解脱出来,将更多的精力投向软件自身的业务功能。由于Java语言的这种简单性,也可以认为Java是一门极好的初学者入门语言。

但是,正如“人无完人”,Java在不少地方依然受到了广大开发人员的诟病,它烦琐的语法经常受到Python等开发人员的嘲笑。在语言的动态性上,甚至也远远不如和它年龄相仿的PHP语言。但为了支持动态语言,Java虚拟机推出了新的函数调用指令invokedynamic(本书将在第11章中具体介绍该指令),试图弥补Java在动态调用上的不足。

值得欣慰的是,到目前为止,Java仍然处于快速发展期,在不断地壮大与完善。1.1.1 那些依托Java虚拟机的语言大咖们

无论受到多少非议,Java的崛起已经是不争的事实。想起《康熙王朝》中的对白,哪一位千古帝王、功臣名将不是“褒满天下,谤满天下”。而且万幸的是,Java生态系统极具活力,在Java 8中,已经推出了函数式编程语法,试图简化Java语言的语法。如果你不喜欢这种新的语法也没关系,Clojure语言作为Lisp的方言,可以很好地在Java虚拟机上执行。如果你受不了Lisp形式的怪异语法,Jython已经可以将Python运行在Java虚拟机上。如果你只需要一个简单的脚本,Groovy也可以成为你的选择。哦,对了,还有Scala,专注于高并发的解决方案。在这里,你可以找到需要的一切。

所有这一切,仍然在不断地蓬勃发展,它们和那个看似呆板的Java语言渐行渐远,但却都深深地扎根于Java虚拟机平台上。1.1.2 Java发展史上的里程碑

下面,将简要介绍一下Java发展史上的重大事项。

1990年,在Sun计算机公司中,由Patrick Naughton、Mike Sheridan及James Gosling领导的小组Green Team开始研发一种可控制家用电子产品的新型计算机软件技术,并希望能够研究出一种可以跨平台的系统。开始他们试着在C++的基础上做修改,但一直无法克服编译器的问题,所以决定自行开发新的程序语言—Oak。这里的Oak已经具备安全性、网络通信、面向对象、垃圾回收、多线程等特性。后来他们发现Oak已经被其他公司注册,于是改名为Java。

1995年,Sun正式发布Java和HotJava产品,Java首次公开亮相。

1996年1月23日Sun Microsystems发布了JDK 1.0。这个版本包括了两部分:运行环境(即JRE)和开发环境(即JDK)。在运行环境中包括了核心API、用户界面API、发布技术、Java虚拟机(JVM)几个部分。开发环境包括了编译Java程序的编译器(即javac)。在JDK 1.0时代,Java使用一款叫作Classic的虚拟机解释执行Java字节码。

1997年2月18日Sun发布了JDK 1.1,在该版本中,已经支持AWT、内部类、JDBC、RMI、反射等特性。同年,Sun收购了一家叫作Longview Technologies的公司,从而获得了Hotspot虚拟机。

同在1997年,Jim Hugunin创造了Jython,但由于各种原因,Jython的发展相当缓慢,但到现在为止,Jython已经取得了长足的进步,甚至已经可以运行Django框架。

1998年,JDK 1.2版本发布(从这个版本开始的Java技术都称为Java 2)。Java 2不仅能兼容智能卡和小型消费类设备,还能兼容大型的服务器系统,它使软件开发商、服务提供商和设备提供商更加容易抢占市场机遇。这一开发工具极大地简化了编程人员编制企业级Web应用的工作。同时Sun发布了JSP/Servlet、EJB规范,将Java分成了J2EE、J2SE和J2ME。这表明了Java开始向企业、桌面应用和移动设备应用3大领域挺进。此时的Java已经做到了解释执行和编译执行混合运行。

2000年,JDK 1.3 发布,Hotspot虚拟机成为Java的默认虚拟机。

2002年,JDK 1.4 发布,古老的Classic虚拟机退出历史舞台。

2003年年底,Java平台的Scala正式发布,同年Groovy也加入了Java阵营。

2004年,JDK 1.5 发布。同时JDK 1.5改名为J2SE 5.0。在这个版本中,Java语言做了大量的改进,比如支持了泛型、注解、自动装箱拆箱、枚举类型、可变长参数、增强的foreach循环等。语法上的简化和改进是这一版本的一大特色。

2006年,JDK 1.6发布。同年,Java开源并建立了OpenJDK。顺理成章,Hotspot虚拟机也成为了OpenJDK中的默认虚拟机。

2007年,Java平台迎来了新伙伴Clojure。

2008年,Oracle收购了BEA,得到了JRockit虚拟机。

2009年,Twitter宣布把后台大部分程序从Ruby迁移到Scala,这是Java平台的又一次大规模应用。

2010年,Oracle收购了Sun,获得最具价值的Hotspot虚拟机。此时,Oracle拥有市场占用率最高的两款虚拟机Hotspot和JRockit,并计划在未来对它们进行整合。

2011年,JDK 1.7发布。在JDK 1.7中,正式启用了新的垃圾回收器G1,支持了64位系统的压缩指针及NIO 2.0,同时新增的invokedynamic指令也是该版本的一大特色。

2014年,JDK 1.8发布。在JDK 1.8中,全新的Lambda表达式是一大亮点,它彻底改变了Java的编程风格和习惯。

Oracle计划在2016年发布JDK 1.9。届时,最令人期待的功能应该是Java的模块化。

2017年,JDK 9发布。在JDK 9中,引入了Java模块化,允许开发者根据项目的需求自定义模块组件,使Java能够更加容易地应用到小型计算设备。

2018年,JDK10发布,引入了var 局部变量类型推断,可以有效地减少代码的冗余,统一了垃圾回收接口,优化了G1 垃圾回收器的并行完整垃圾回收。

注意:在本书中,JDK 1.6等同于JDK 6,JDK 1.7等同于JDK 7,JDK 1.8等同于JDK 8,JDK 1.9等同于JDK 9,JDK 1.10等同于JDK 10。1.2 跨平台的真相:Java虚拟机做中介

在简单了解了Java的发展历程之后,本节将着重介绍Java虚拟机的概念,最后了解一下目前最流行的Java虚拟机。1.2.1 理解Java虚拟机的原理

所谓虚拟机,就是一台虚拟的计算机。它是一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。大名鼎鼎的Visual Box、VMware就属于系统虚拟机,它们完全是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台。程序虚拟机的典型代表就是Java虚拟机,它专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令我们称为Java字节码指令。无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。

图1.1显示了同一个Java程序(Java字节码的集合)通过Java虚拟机运行于各大主流系统平台,该程序以虚拟机为中介,实现了跨平台的特性。图1.1 在操作系统之上执行的虚拟机程序1.2.2 看清Java虚拟机的种类

Java发展至今,先后出现了不少Java虚拟机。在Java发展最初,Sun使用的是一款叫作Classic的Java虚拟机,之后,在Solaris平台上还曾短暂地使用过Exact VM虚拟机,到现在,最终被大规模部署和应用的是Hotspot虚拟机。

除了Sun公司,各大公司及组织都曾积极研发过Java虚拟机,比如BEA的JRockit,目前,JRockit和Hotspot都被收入Oracle旗下,大有整合的趋势。在IBM内部,使用着一款名为J9的虚拟机,广泛用于IBM的各大产品(如果当年IBM成功收购了Sun,那么很可能是J9和Hotspot进行整合了)。此外,Apache也曾经推出过与JDK 1.5和JDK 1.6 兼容的Java运行平台Apache Harmony,它是开源软件,但受到同样开源的OpenJDK的压制,最终于2011年退役,虽然目前并没有Apache Harmony被大规模商用的案例,但是它的出现对Android的发展起到了极为重要的作用。在嵌入式领域,KVM和CDC/CLDC Hotspot两款虚拟机也扮演着重要的角色,在iOS和Android盛行之前,这两款虚拟机也广泛运用于手机平台。

注意:由于目前Hotspot占有绝对的市场地位,若无特别说明,本书的示例及参数都是针对Hotspot虚拟机的。1.3 一切看我的:Java语言规范

讲到Java虚拟机,就不得不说Java,说到Java,就不得不提Java语言规范(Java Language Specification)。Java语言规范和Java虚拟机规范目前都可以在Oracle的官方网站上找到(http://docs.oracle.com/javase/specs/)。本节将简要介绍一下Java语言规范。

Java语言规范是用来描述Java语言的,它定义了Java语言的语言特性,比如Java的语法、词法、支持的数据类型、变量类型、数据类型转换的约定、数组、异常等内容。Java语言规范的目的是告诉开发人员“Java代码应该如何编写”。

本节将简单介绍几个在Java语言规范中定义的典型内容,帮助读者理解这份规范的意图。1.3.1 词法的定义

词法规定了Java语法中每一个单词如何书写才是合乎规范的。比如,词法定义中规定了Java的关键字,如果开发人员使用Java的关键字作为变量名,显然无法通过编译。

下面简单地看一个有关标识符的定义:

这里定义了标识符由一个标识符串组成,但是不能是关键字、布尔字面量或者Null字面量。而标识符串是一个由字母开头的、由字母或数字构成的串。这里的字母或数字并非简单的ABC,而是指任意的Unicode字符。

根据这个定义便可以知道,下面的方法是不符合规范的:

因为new为Java关键字,不属于标识符,因此不能用作方法名,而下述代码则是符合规范的:

虽然上述代码使用了中文作为方法名,但是根据定义,“打”、“印”属于Unicode字符,因此是符合规范的名字,也构成了符合规范的标识符。

有关词法另一个典型的案例就是数字的表示。在JDK 1.7中,以下都属于符合规范的数字:

· 整数

○ 0、2、0372、0xDada_Cafe、1996、0x00_FF__00_FF

· 长整形

○ 01、0777L、0x100000000L、2_147_483_648L、0xC0B0L

· 单精度浮点

○ 1e1f、2.f、.3f、0f、3.14f、6.022137e+23f

· 双精度浮点

○ 1e1、2.、.3、0.0、3.14、1e-9d、1e137

就直观感觉而言,上述有些数字真是长得太奇怪了,比如0xDada_Cafe,它不更像一个英语单词吗?而事实上,这是一个符合规范的16进制整数。Java语言规范规定,以0x字符开始的数表示16进制,同时,为了可读性,也允许在数字中间增加下画线进行分割(这是JDK 1.7中引入的)。

得益于Java语言规范对于词法的定义,现在Java程序可以使用更加丰富的方法来定义数字,无论是在可读性还是在表达能力上,都非常强劲。1.3.2 语法的定义

词法定义规定了什么样的单词是合理的,语法定义规定了什么样的语句是合乎规范的。以if语句为例,在类似于Basic的语言中,可能会用以下形式定义if语句:

但是在Java中给出了这样的定义:

即在一个if语句中,表示条件的表达式必须用小括号表示,同时在右小括号后,书写语句块,表示执行内容。而对于Expression和Statement的具体定义,在语言规范中也有十分详细的描述,这里就不一一展开了,有兴趣的读者可以参考Java语言规范,JDK 1.7第14章的内容“Blocks and Statements”。1.3.3 数据类型的定义

Java语言规范中还定义了Java的数据类型。根据Java 1.7的规范,Java的数据类型分为原始数据类型和引用数据类型。原始数据类型又分为数字型和布尔型。数字型又有byte、short、int、long、char、float、double。注意,在这里char被定义为整数型,并且在规范中明确定义:byte、short、int、long分别是8位、16位、32位、64位有符号整数,而char为16位无符号整数,表示UTF-16的字符。布尔型只有两种取值:true和false。而对于float和double,规范中规定,它们是满足IEEE 754的32位浮点数和64位浮点数。

注意:在Java语言中,char占2字节,而不是C语言中的1字节。从这点上看,Java的国际化在语言底层就提供了强有力的支持。

此外,规范还定义了各类数字的取值范围、初始值,以及能够支持的各种操作。以整数为例,比较运算、数值运算、位运算、自增自减运算等都在规范中有描述。

除了基本数据类型,引用数据类型也是Java重要的组成部分,引用数据类型分为3种:类或接口、泛型类型和数组类型。

提醒:引用类型和原始类型在Java的处理中是截然不同的,尤其对于它们的“相等”操作。【示例1-1】在Java语言规范中,有一个简短的示例,说明了引用类型和原始类型的区别:

上述程序将输出:

从上述输出可以看出,对于原始数据类型int,i1和i2表示不同的变量,两者毫无关系,但是对于v1和v2,它们都指向唯一一个由new关键字创建的Value对象。

由于本书并非讲解Java语言,因此对于这部分内容点到即止,有兴趣的读者可以参考Java语言规范的第4章“Types,Values,and Variables”。1.3.4 Java语言规范总结

除上述基本内容外,Java语言规范还定义了各种不同类型间的转换规则、方法的可见性定义、有关接口的使用、注释等。

总之,Java语言规范完整定义和描述了Java语言的所有特性,因为Java语言本身不属于本书的讨论重点,故在此只做简要介绍。1.4 一切听我的:Java虚拟机规范

虽然Java语言和Java虚拟机有着密切的联系,但两者是完全不同的内容。Java虚拟机是一台执行Java字节码的虚拟计算机,它拥有独立的运行机制,其运行的Java字节码也未必由Java语言编译而成,像Groovy、Scala等语言生成的Java字节码也可以由Java虚拟机执行。立足于Java虚拟机,可以产生各种各样的跨平台语言。除了语言特性各不相同,它们可以共享Java虚拟机带来的跨平台性、优秀的垃圾回收器,以及可靠的即时编译器。

因此,与Java语言不同,Java虚拟机是一个高效的、性能优异的、商用级别的软件运行和开发平台,而这也是本书讨论的重点。

Java虚拟机规范的主要内容大概有以下几个部分:

· 定义了虚拟机的内部结构(将在第2章中详细介绍)。

· 定义了虚拟机执行的字节码类型和功能(将在第11章中详细介绍)。

· 定义了Class文件的结构(将在第9章中详细介绍)。

· 定义了类的装载、连接和初始化(将在第10章中详细介绍)。

以Java 1.7为例,读者可以在http://docs.oracle.com/javase/specs/jvms/se7/html/浏览虚拟机规范全文。这份规范可以说是Java虚拟机的指导性文件,如果要实现自定义的Java虚拟机,则需要参考和熟悉这份规范,同时这份规范对于了解现存的流行Java虚拟机(如Hotspot、IBM J9等),也有十分重要的意义。1.5 数字编码就是计算机世界的水和电

数字是计算机内最直接、最基础的表现类型。了解数字在计算机内的表示,对于了解整个计算机系统具有相当重要的作用,了解数字也是专业计算机从业人员的基本功之一。本节将主要介绍整数及浮点数在Java虚拟机中的表示。1.5.1 整数在Java虚拟机中的表示

在Java虚拟机中,整数有byte、short、int、long四种,分别表示8位、16位、32位、64位有符号整数。整数在计算机中使用补码表示,在Java虚拟机中也不例外。在学习补码之前,必须先理解原码和反码。

所谓原码,就是符号位加上数字的二进制表示。以int为例,第1位表示符号位(正数或者负数),其余31位表示该数字的二进制值。

10的原码为:00000000 00000000 00000000 00001010

-10的原码为:10000000 00000000 00000000 00001010

对于原码来说,绝对值相同的正数和负数只有符号位不同。

反码就是在原码的基础上,符号位不变,其余位取反,以-10为例,其反码为:

11111111111111111111111111110101

负数的补码就是反码加1,整数的补码就是原码本身。

因此,10的补码为:

00000000 00000000 00000000 00001010

而-10的补码为:

11111111111111111111111111110110

在Java中,可以使用位运算查看整数中每一位的实际值,方法如下:

以上代码将打印-10在虚拟机内的实际表示,程序的执行结果如下:

可以看到,这个结果和之前补码的计算结果是完全匹配的。

这段程序的基本思想是:进行32次循环(因为int有32位),每次循环取出int值中的一位,第3行的0x80000000是一个首位为1、其余位为0的整数,通过右移i位,定位到要获取的第i位,并将除该位外的其他位统一设置为0,而该位不变,最后将该位移至最右,并进行输出。

相对于原码,使用补码作为计算机内的实际存储方式至少有以下两个好处。(1)可以统一数字0的表示。由于0既非正数,又非负数,使用原码表示时符号位难以确定,把0归入正数或者负数得到的原码是不同的。但是使用补码表示时,无论把0归入正数还是负数都会得到相同的结果。计算过程如下。

如果0为正数,则补码为原码本身:00000000 00000000 00000000 00000000。

如果0为负数,则补码为反码加1,负数0的原码为:

10000000 00000000 00000000 00000000

反码为:

11111111 11111111 11111111 11111111

补码在反码的基础上加1,结果为:

00000000 00000000 00000000 00000000

可以看到,使用补码作为整数编码,可以解决数字0的存储问题。(2)使用补码可以简化整数的加减法计算,将减法计算视为加法计算,实现减法和加法的完全统一,实现正数和负数加法的统一。现使用8位(byte)整数说明这个问题。

计算-6+5的过程如下。

-6的补码:11111010

5的补码:00000101

直接相加得:11111111

通过计算可知,11111111表示-1。

计算4+6的过程如下。

4的补码:00000100

6的补码:00000110

直接相加得:00001010

通过计算可知,00001010表示10(十进制)。

可以看到,使用补码表示时,只需要将补码简单地相加,即可得到算术加法的正确结果,而无须区别正数或者负数。1.5.2 浮点数在Java虚拟机中的表示

在Java虚拟机中,浮点数有float和double两种,分别是32位和64位浮点数。浮点数在虚拟机中的表示比整数略显复杂。目前,使用最为广泛的是由IEEE 754定义的浮点数格式。目前Java虚拟机中对于浮点数的处理参考IEEE 754的规范。本节将以float为例,简要介绍浮点数的表示方法。

在IEEE 754的定义中,一个浮点数由3部分组成,分别是符号位、指数位和尾数位。以32位float类型为例,符号位占1位,表示正负数,指数位占8位,尾数位占剩余的23位,如图1.2所示。

其中,sflag表示符号,当s为0时,sflag为1,当s为1时,sflag为-1。m为尾数值,实际占用空间为23位,但是根据e的取值,有24位精度。当e全为0时,尾数位附加为0,否则,尾数位附加为1。e为指数位,用8位表示。图1.2 浮点数格式

以浮点数-5为例,其内部表示为:

1 10000001 01000000000000000000000

符号位为1表示负数,指数位为10000001,表示129。

尾数位为:01000000000000000000000。因为e不全为0,故实际的尾数位为:

101000000000000000000000

尾数位表示2的指数次幂的和,每一位表示求和数列中的对应项是否为0,这里表示:012345

1*2+0*2-+1*2-+0*2-+0*2-+0*2-+…,对应关系如图1.3所示。图1.3 尾数的计算

故1 10000001 01000000000000000000000的值为:(129-127)012345

-1*2*(1*2+0*2-+1*2-+0*2-+0*2-+0*2-)=-1*4*1.25=-5

float浮点数还可以表示一些特殊的数字,如表1.1所示。表1.1 float的特殊数字

其中,指数位全为1表示无穷大和NaN等特殊数字。指数位全为0为非规范化的浮点数。【示例1-2】在Java中,使用Float.floatToRawIntBits()函数可以获得一个单精度浮点数的IEEE 754表示。以下代码打印了-5的内部表示:

程序运行结果为:

其中,Float.floatToRawIntBits()函数最终由native方法实现,具体实现如下:

从上述代码可以看出,为了获取float的内部表示,使用了C语言中的union自然实现这个转换。1.6 抛砖引玉:编译和调试虚拟机

如果要对虚拟机进行深入的研究,那么编译和调试Java虚拟机是必不可少的。为何要编译自己的虚拟机呢?主要原因有两点。

第一,通过自己编译可以得到一个debug或者fastdebug版本的调试用虚拟机,调试用虚拟机可以支持更多的虚拟机参数,这些开发专用的虚拟机参数可以帮助开发人员获得更多的虚拟机内部信息,而这些参数在正常发行版本中是无法使用的。因此考虑到本书的实用性,本书并不会过多介绍那些只在调试版本中使用的参数,但如果读者有兴趣,可以编译自己的虚拟机,进行尝试。

第二,使用自己编译的调试版虚拟机可以进行虚拟机代码的单步调试,有利于实现对虚拟机代码的理解。由于虚拟机代码比较复杂,仅通过代码阅读很难深入理解其实现机制,有时不得不依靠单步调试,而编译后调试版本可以帮助你实现这一功能。

为编译虚拟机,首先必须获得虚拟机源码,读者可以使用下面的命令获取JDK10的源码。推荐读者使用较新的版本,因为老版本的编译脚本可能在某些平台上存在问题。

笔者的编译环境为Ubuntu系统,读者可以选择自己喜欢的Linux发行版进行编译。编译虚拟机之前,还必须做一些准备工作。

1.编译前的准备工作(1)安装好依赖库。在Ubuntu平台上可以通过apt-get命令安装,在CentOS平台上可以通过yum命令安装。比如,笔者在编译前至少安装了以下库:

如果依赖库安装不全,在编译过程中就会提示错误,从错误提示中,读者应该可以得知缺少了哪些库或者头文件,进行安装即可。当然,gcc和g++作为基本的编译工具也是必须要安装的。(2)准备一个Boot JDK。Boot JDK用于OpenJDK编译的执行。笔者在这里使用的是JDK8,也推荐读者使用JDK8作为JDK10的Boot JDK。

2.准备编译

准备就绪之后,就可以开始编译了。(1)进入解压后的openjdk目录:(2)执行configure脚本配置编译选项,笔者的配置如下:

在该脚本中,-with-debug-level=slowdebug代表要编译debug版本JDK;-enable-dtrace代表开启dtrace;-with-target-bits=64代表编译64位JVM;-with-memory-size=3000代表编译JDK的计算机至少需要3GB,根据计算机不同配置可以设置不同的值;-disable-warnings-as-errors代表忽略警告的信息;-enable-native-debug-symbols=internal代表生成symbol文件,这个便于后续的动态调试;-with-boot-jdk=/opt/jdk1.8.0_181/代表Boot JDK是JDK1.8。更多的编译选项可以参考JDK源码根目录的README文件。

配置成功会显示下面的信息:(3)通过make images命令执行整个编译,将会生成debug版本的虚拟机。编译的过程可能会花费比较长的时间,一般来说,编译一个版本可能需要15~45分钟,视计算机性能而定。当编译成功后,会有以下输出:

进入build目录,可以看到编译的结果,下面显示了debug版本的编译结果:

有了debug版本的虚拟机,就可以使用gdb对虚拟机进行调试了。接下来将简单地演示Java虚拟机的调试方法。

3.Java虚拟机的调试

笔者选用linux-x86_64-normal-server-slowdebug下的虚拟机对虚拟机的源码进行调试。(1)进入linux-x86_64-normal-server-slowdebug/jdk/bin目录,用gdb启动Java可执行程序。(2)进入gdb命令行环境:

在main函数中打断点:

执行run,继续运行:

可以看到,当前程序停在了java.base/share/native/launcher/main.c文件的第97行。使用命令next(缩写为n)进行单步调试。

至此,读者就可以使用这套环境作为辅助,更好地深入Java虚拟机内部了。1.7 小结

本章主要介绍了Java语言和Java虚拟机的发展历史,并介绍了Java生态环境中两份非常重要的规范—Java语言规范和Java虚拟机规范。其中Java虚拟机规范为本书后续讨论的重点内容。同时,作为了解Java虚拟机的第一步,简要介绍了整数和浮点数在Java虚拟机中的表示。最后,为了方便读者更加深入地了解虚拟机,本章还给出了单步调试Java虚拟机的方法。

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

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载