初识Java虚拟机
1.Java发展历史
- 1990年,在Sun计算机公司中,由Patrick Naughton、MikeSheridan及James Gosling领导的小组Green Team,开始研发一种可控制家用电子产品的新型计算机软件技术,并希望能够研究出一种可以跨平台的系统。起先他们试着在C+的功能基础上做修改,但一直无法克服编译器的问题,所以决定自行开发新的程序语言Oak。这里的Ok已经具备安全性、网络通信、面向对象、垃圾回收、多线程等特性。后来他们发现Oak已经被其他公司注册,于是改名为Java。
- 1995年,Sun正式发布Java和HotJava产品,Java首次公开亮相。
- 1996年1月23日Sun Microsystems发布了JDK1.0。这个版本包括了两部分:运行环境(即RE)和开发环境(即JDK)。在运行环境中包括了核心API、用户界面API、发布技术、Java虚拟机(JVM)几个部分。开发环境包括了编译Java程序的编译器(即javac)。在JDKl.0时代,Java使用一款叫作Classic的虚拟机解释执行Java字节码。
- 1997年2月18日Sum发布了JDK1.1,在该版本中,已经支持AWT、内部类、JDBC、RMI、反射等特性。同年,Sun收购了一家叫作Longview Technologies的公司,从而获得了Hotspot虚拟机。
- 同在1997年,Jim Hugunin创造了Jython,但由于各种原因,Jython的进展相当缓慢,但到现在为止,Jython已经取得了长足的进步,甚至已经可以运行Django框架。
- 1998年,JDK1.2版本发布(从这个版本开始的Java技术都称为Java2)。Java2不仅能兼容智能卡和小型消费类设备,还能兼容大型的服务器系统,它使软件开发商、服务提供商和设备提供商更加容易地抢占市场机遇。这一开发工具极大地简化了编程人员编制企业级Wb应用的工作。同时Sun发布了JSP/Servlet,EJB规范,以及将Java分成了J2EE、J2SE和J2ME.这表明了Java开始向企业、桌面应用和移动设备应用3大领域挺进。此时的Java已经做到解释执行和编译执行混合运行。
- 2000年,JDK1.3发布,Hotspot虚拟机成为Java的默认虚拟机。
- 2002年,JDK1.4发布,古老的Classic虚拟机退出历史舞台。
- 2003年年底,Java平台的Scala正式发布,同年Groovy也加入了Java阵营。
- 2004年,DK1.5发布。同时JDK1.5改名为J2SE5.0。在这个版本中,Java语言做了大量的改进,比如支持了泛型、注解、自动装箱拆箱、枚举类型、可变长参数、增强的foreach循环等。语法上的简化和改进是这一版本的一大特色。
- 2006年,JDK1.6发布。同年,Java开源并建立了OpenJDK。顺理成章,Hotspot虚拟机也成为了OpenJDK中的默认虚拟机。
- 2007年,Java平台迎来了新伙伴Clojure。
- 2008年,Oracle收购了BEA,得到了JRockit虚拟机。
- 2009年,Twitter宣布把后台大部分程序从Ruby迁移到Scala,这是Java平台的又一次大规模应用。
- 20l0年,Oracle收购了Sun,获得最具价值的Hotspot虚拟机。此时,Oracle拥有市场占用率最高的两款虚拟机Hotspot和JRockit,.并计划在未来对它们进行整合。
- 2011年,JDK1.7发布。在JDK1.7中,正式启用了新的垃圾回收器G1,支持了64位系统的压缩指针,以及NO2.O,同时新增的invokedynamic指令也是该版本的一大特色
- 2014年,JDK1.8发布。在JDK1.8中,全新的Lambda表达式是一大亮点,它彻底改变了Java的编程风格和习惯。
- Oracle计划在2016年,发布JDK1.9。届时,最令人期待的功能应该是Java的模块化
2.Java虚拟机的原理和分类
2.1 什么是虚拟机
所谓虚拟机,就是一台虚拟的计算机。它是一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。大名鼎鼎的Visual Box、VMware就属于系统虚拟机,它们完全是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台。程序虚拟机的典型代表就是Java虚拟机,它专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令我们称为Java字节码指令。无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。
通过Java虚拟机可以让Java程序实现跨平台的特性。如下图所示:
2.2 Java虚拟机的种类
- 在Java发展最初,Sum使用的是一款叫作Classic的Java虚拟机之后,在Solaris平台上还曾短暂地使用过Exact VM虚拟机,到现在,最终被大规模部署和应用的是Hotspot虚拟机。
- 除了Sun公司以外,各大公司以及组织都曾积极研发过Java虚拟机,比如BEA的JRockit,目前,Rockit和Hotspot都被收入Oracle旗下,大有整合的趋势。在IBM内部,使用着一款名为J9的虚拟机,广泛用于IBM的各大产品(如果当年IBM成功收购了Sun,那么很可能是J9和Hotspot进行整合了)。
- 此外,Apache也曾经推出过与JDK1.5和JDK1.6兼容的Java运行平台Apache Harmony,它是开源软件,但受到同样开源的OpenJDK的压制,最终于2011年退役,
- 虽然目前并没有Apache Harmony被大规模商用的案例,但是它的出现对Android的发展起到了极为重要的作用。
- 在嵌入式领域,KVM和CDC/CLDC Hotspot两款虚拟机也粉演着重要的角色,在iOS和Android盛行之前,这两款虚拟机也广泛运用于手机平台。
- 注意:由于目前Hotspot占有绝对的市场地位,
3.Java语言规范——一切看我的
Java语言规范和Java虚拟机规范目前都可以在Oracle的官方网站上找到(http://docs.oracle.com/javase/specs/)。下面简要介绍一下Java语言规范。
Java语言规范是用来描述Java语言的,它定义了Java语言的语言特性,比如Java的语法词法、支持的数据类型、变量类型、数据类型转换的约定、数组、异常等内容。Java语言规范的目的是告诉开发人员“Java代码应该如何编写”。
每一种开发语言都会定义词法,语法,数据类型相关的规范,Java语言也是。
-
词法的定义
词法规定了Java语法中每一个单词如何书写才是合乎规范的。比如,词法定义中规定了Java的关键字,如果开发人员使用Java的关键字作为变量名,显然无法正常通过编译。有关词法另一个典型的案例就是数字的表示,比如:0372,0x00_FF_00_FF,2.f,0.0等都是合法的数字。得益于Java语言规范对于词法的定义,现在Java程序可以使用更加丰富的方法来定义数字,无论是在可读性或是在表达能力上,都非常强劲。
-
语法的定义
语法定义规定了什么样的语句是合乎规范的。以if语句为例,在类似于Basic的语言中,可能会用以下形式定义if语句:
if Expression then Statement end if
但是在Java中给出了这样的定义:
IfThenStatement: if Expression Statement
-
数据类型的定义
Java语言规范中还定义了Java的数据类型。Java的数据类型分为原始数据类型和引用数据类型。原始数据类型又分为数字型和布尔型。数字型又有byte、short、int、long、char、foat、double。注意,在这里char被定义为整数型,并且在规范中明确定义:byte、short、int、long分别是8位、16位、32位、64位有符号整数,而char为16位无符号整数,表示UTF-l6的字符。布尔型只有两种取值:true和false。而对于float和double,规范中规定,它们是满足EEE754的32位浮点数和64位浮点数。此外,规范还定义了各类数字的取值范围、初始值,以及能够支持的各种操作。以整数为例,比较运算、数值运算、位运算、自增自减运算等都在规范中有描述。
除了基本数据类型外,引用数据类型也是Java重要的组成部分,引用数据类型分为3种:类或接口、泛型类型以及数组类型。 -
Java语言规范总结
除上述基本内容外,Jva语言规范还定义了各种不同类型间的转换规则、方法的可见性定义、有关接口的使用、注释等。
总之,Java语言规范完整定义和描述了Java语言的所有特性,但是这些内容不属于JVM的内容,所以上面简单的介绍了一下。
4.Java虚拟机的规范——一切听我的
虽然Java语言和Java虚拟机有着密切的联系,但两者是完全不同的内容。Java虚拟机是一台执行Java字节码的虚拟计算机,它拥有独立的运行机制,其运行的Java字节码也未必由Java语言编译而成,像Groovy、Scala等语言生成的Java字节码也可以由Java虚拟机执行。立足于Java虚拟机,可以产生各种各样的跨平台语言。除了语言特性各不相同外,它们可以共享Java虚拟机带来的跨平台性、优秀的垃圾回收器,以及可靠的即时编译器。
因此,与Java语言不同,Java虚拟机是一个高效的、性能优异的、商用级别的软件运行和开发平台,而这也是我们学习的重点。
Java虚拟机规范的主要内容大概有以下几个部分:
- 定义了虚拟机的内部结构。
- 定义了虚拟机执行的字节码类型和功能。
- 定义了Class文件的结构。
- 定义了类的装载、连接和初始。
以后的例题都是以JDK8来讲的,你可以在https://docs.oracle.com/javase/specs/jvms/se8/html/index.html浏览虚拟机规范全文。这份规范可以说是开发Java虚拟机的指导性文件,如果要实现自定义的Java虚拟机,则需要参考和熟悉这份规范,同时这份规范对于了解现存的流行Java虚拟机(如Hotspot、IBM J9等),也有十分重要的意义。
5.Java虚拟机中的数字的表示
数字是计算机内最直接、最基础的表现类型。了解数字在计算机内的表示,对于了解整个
计算机系统具有相当重要的作用,数字也是专业计算机从业人员的基本功之一。下面我们主要介绍整数以及浮点数在Java虚拟机中的支持情况。
5.1 整数在Java虚拟机中的表示
在Java虚拟机中,整数有byte、short、int、long四种,分别表示8位、16位、32位、64位有符号整数。整数在计算机中使用补码表示,在Java虚拟机中也不例外。在学习补码之前,必须先理解原码和反码。所谓原码,就是符号位加上数字的二进制表示。以int为例,第1位表示符号位(正数或者负数),其余31位表示该数字的二进制值。
10的原码为:00000000000000000000000000001010
-10的原码为:10000000000000000000000000001010
#对于原码来说,绝对值相同的正数和负数只有符号位不同。
反码就是在原码的基础上,符号位不变,其余位取反,以-10为例,其反码为:11111111111111111111111111110101
负数的补码就是反码加1,正数的补码就是原码本身。
Java中可以使用位运算符查看整数中每一位的实际值,方法如下:
int a = -10;
for (int i =0 ; i < 32;i++){
int t = (a&0x80000000>>>i)>>>(31-i);
System.out.print(t);
}//输出结果是:11111111111111111111111111110101
这段程序的基本思想是:进行32次循环(因为int有32位),每次循环取出int值中的位,第3行0x80000000是一个首位为1、其余位为0的数,通过右移i位,定位到要获取的第ⅰ位,并将除该位外的其他位统一设置为0,而该位不变,最后将该位移至最右,并进行输出。
为什么使用补码作为计算机内的实际存储方式而不是原码呢?
-
可以统一数字0的表示。由于0既非正数,又非负数,使用原码表示时符号位难以确定,把0归入正数或者负数得到的原码编码是不同的。但是使用补码表示时,无论把0归入正数或者负数都会得到相同的结果。
-
使用补码可以简化整数的加减法计算,将减法计算视为加法计算,实现正数和负数加法的统一。现使用8位(byte)整数说明这个问题。
-- 计算-6+5的过程如下。 -6补码:11111010 5补码:00000101 直接相加得:11111111 -- 通过计算可知,11111111表示-1。 -- 计算4+6的过程如下。 4的补码:00000100 6的补码:00000110 直接相加得:00001010 -- 通过计算可知,00001010表示10(十进制)。
根据以上例子可以看到,使用补码表示时,只需要将补码简单地相加,即可得到算术加减法的正确结果,而无须区别正数或者负数。
5.2 浮点数在Java虚拟机中的表示
在Java虚拟机中,浮点数有float和double两种,分别是32位和64位浮点数。浮点数在虚拟机中的表示比整数略显复杂。目前,使用最为广泛的是由IEEE754定义的浮点数格式。目前Java虚拟机中对于浮点数的处理参考IEEE754的规范。下面将以float为例,简要介绍浮点数的表示方法。
在IEEE754的定义中,一个浮点数由3部分组成,分别是:符号位、指数位和尾数位。以32位flot为例,符号位占1位,表示正负数,指数位占8位,尾数位占剩余的23位,如下图:
浮点数取值公式是:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lafcRVbA-1649954354912)(https://juejin.cn/equation?tex=sflag * m * 2^{(e-127)]})
- 其中,sflag表示符号,当s为0时,sf1ag为1,当s为1时,sflag为-1。
- m为尾数值,实际占用空间为23位,但是根据e的取值,有24位精度。当e全为0时,尾数位附加为0,否
则,尾数位附加为1。 - e为指数位,用8位表示。
举例说明,-5的表示为:1 1000 0001 010 0000 00000000 00000000
- 尾数位原来是
010 0000 00000000 00000000
,但是指数位1000 0001
不全为0,所以尾数位(从左边)附加1,(不能当做算法的加1,当做字符串的拼接那样子理解好点)变成这样子:
1 + 010 0000 00000000 00000000 = 1010 0000 00000000 00000000
套公式计算:
计算过程:
-
m的计算复杂点,尾数位表示2的指数次方的和。计算过程
-
所以: