JVM 是Java Virtual Machine(JVM )的缩写,JVM 是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
Java 虚拟机源于由James Gosling 在1992年设计,用于支持Oak 程序语言的虚拟机。在Java虚拟机的发展历程中,Sun 的Green 项目、FirstPerson 公司、LiveOak 项目、Java 产品组、JavaSoft 公司以及今天的Oralce 公司的Java 平台组中许多人都做出了直接或间接的贡献。
Java虚拟机的特点如下:
基于栈的虚拟机:Intel x86 和ARM 这两种最常见的计算机体系的机构都是基于寄存器的。不同的是,JVM 是基于栈的。
符号引用:除了基本类型以外的数据(类和接口)都是通过符号来引用,而不是通过显式地使用内存地址来引用。
垃圾回收机制:类的实例都是通过用户代码进行创建,并且自动被垃圾回收机制进行回收。
通过对基本类型的清晰定义来保证平台独立性:传统的编程语言,例如C/C++,int类型的大小取决于不同的平台。JVM通过对基本类型的清晰定义来保证它的兼容性以及平台独立性。
网络字节码顺序:Java class文件用网络字节码顺序来进行存储:为了保证和小端的Intel x86架构以及大端的RISC系列的架构保持无关性,JVM使用用于网络传输的网络字节顺序,也就是大端。
虽然是Sun公司开发了Java,但是所有的开发商都可以开发并且提供遵循Java 虚拟机规范的JVM。正是由于这个原因,使得Oracle HotSpot和IBM JVM 等不同的JVM 能够并存。Google的Android系统里的Dalvik VM 也是一种JVM,虽然它并不遵循Java 虚拟机规范。和基于栈的Java 虚拟机不同,Dalvik VM 是基于寄存器的架构,因此它的Java 字节码也被转化成基于寄存器的指令集。
设计虚拟机的初衷是让Java 能够通过它来实现WORA(Write Once Run Anywhere 一次编译,到处运行)。因此,JVM 可以在不修改Java 代码的情况下,在所有的硬件环境上运行Java 字节码,当然这需要你在不同的系统上安装相应版本的虚拟机。
一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java 语言虚拟机后,Java 语言在不同平台上运行时不需要重新编译。Java 语言使用JVM 屏蔽了与具体平台相关的信息,使得Java 语言编译程序只需生成在JVM 上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM 在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java 的能够“一次编译,到处运行”的原因。
JVM 与JDK、JRE的关系从下图可以很清晰的了解,蓝色部分为JVM ,JVM 从属于JRE :
从图中看到JRE 主要是由JavaSE API、JVM 和部署技术组成。JVM 的主要作用是通过Class Loader 来加载Java 程序,并且按照JavaSE API 来执行加载的程序。
类似的运行一个Java程序需要经历以下几个步骤:
首先,要编写一个Java 程序,将其保存为.java 类型文件;
然后,通过JDK bin目录下的javac 应用程序将.java 文件编译成.class 类型文件;
最后,通过JDK bin目录下的java 应用程序执行.class 文件,输出结果。
我们很容易就会判断出前两步应该不属于JVM 范畴,因为JVM 最大的特点就是“执行Java程序”,可以简单的理解为只有需要运行的Java 程序才会涉及到JVM 。程序开始执行时JVM 运行,程序结束后JVM也随之停止。你在同一台机器上运行三个程序,就会有三个运行中的JVM 进程。
JVM 总是开始于一个main() 方法,这个方法必须是公有、返回void、直接受一个字符串数组。在程序执行时,必须给JVM 指定main() 方法所在类。
main() 方法是程序的起点,他被执行的线程初始化为程序的初始线程。程序中其他的线程都由他来启动。Java 中的线程分为两种:守护线程(daemon)和普通线程(non-daemon)。守护线程是JVM 自己使用的线程,比如负责垃圾收集的线程就是一个守护线程。当然,你也可以把自己的程序设置为守护线程,这里main() 方法的初始线程不是守护线程。
只要JVM 中还有普通的线程在执行,JVM 就不会停止。如果有足够的权限,你可以调用exit()方法终止程序。
从进程管理器中就可以清晰的看到,当我运行3个HelloWorld 程序后,进程管理器中出现了3个javaw 进程,并且全部为普通进程,如图所示:
那么JVM到底是一种概念还是一种物理结构呢?弄清楚JVM 到底是什么对JVM 的学习至关重要!
首先从JVM 结构说起,JVM 结构如下图所示:
JVM 由一个"class loader subsystem"(类加载器子系统:用于加载符合JVM 规格的类型(类或接口)、"execution engine"(执行引擎:负责执行已经加载类中的方法)和"runtime data areas"(运行时数据区:用于存储运行时数据) 这三部分组成。
类装载器子系统(class loader subsystem),顾名思义,就是用来装载.class 文件的。JVM 的两种类装载器包括:启动类装载器和用户自定义类装载器,启动类装载器是JVM 实现的一部分,用户自定义类装载器则是Java 程序的一部分,必须是ClassLoader 类的子类。
执行引擎(execution engine):执行字节码或本地方法。
运行时数据区(runtime data areas):包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域这5部分组成,如图所示:
此时已经无法用一个狭义的概念去定义JVM ,既可以将之理解为类似其他虚拟机一样具备虚拟计算机功能的软件,又可以理解为Java 运行时的一种系统结构,所以理解JVM 的概念应该是一个循序渐进、不断完善JVM 相关知识组成的过程。
JVM 可以操作的数据类型可分为两类:原始类型(Primitive Types,也经常翻译为原生类型或者基本类型)和引用类型(Reference Types)。与之对应,也存在有原始值(Primitive Values)和引用值(Reference Values)两种类型的数值可用于变量赋值、参数传递、方法返回和运算操作。
JVM 有9种数据类型,其中前7种为基本数据类型,他们是:
Type | Range |
byte | 8-bit signed two's complement integer (-27 to 27 - 1, inclusive) |
short | 16-bit signed two's complement integer (-215 to 215 - 1, inclusive) |
int | 32-bit signed two's complement integer (-231 to 231 - 1, inclusive) |
long | 64-bit signed two's complement integer (-263 to 263 - 1, inclusive) |
char | 16-bit unsigned Unicode character (0 to 216 - 1, inclusive) |
float | 32-bit IEEE 754 single-precision float |
double | 64-bit IEEE 754 double-precision float |
returnAddress | address of an opcode witdin tde same metdod |
reference | reference to an object on tde heap, or null |
他们的关系体系如下图所示:
JVM 希望尽可能多的类型检查能在程序运行之前完成,换句话说,编译器应当在编译期间尽最大努力完成可能的类型检查,使得虚拟机在运行期间无需进行这些操作。原始类型的值不需要通过特殊标记或别的额外识别手段来在运行期确定它们的实际数据类型,也无需刻意将它们与引用类型的值区分开来,虚拟机的字节码指令本身就可以确定它的指令操作数的类型是什么,所以可以利用这种特性即可直接确定操作数的数值类型。举个例子,iadd、ladd、fadd和dadd这几条指令的操作含义都是将两个数值相加,并返个相加的结果,但是每一条指令都有自己的专属操作数类型,此处按顺序分别为:int、long、float和double。
虚拟机没有给boolean(布尔)类型设置单独的指令。boolean型的数据是由integer指令,包括integer返回来处理的。boolean型的数组则是用byte数组来处理的。虚拟机使用IEEE754格式的浮点数。不支持IEEE格式的较旧的计算机,在运行Java数值计算程序时,可能会非常慢。
注:Java数组被当做object处理。
以上就是对JVM 的一些介绍,相关JVM 的名词解释及概念会以专题文章形式展现。
以下为参考文献:
http://www.artima.com/insidejvm/ed2/jvmP.html
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html