java

位置:IT落伍者 >> java >> 浏览文章

深究Java虚拟机


发布日期:2019年05月23日
 
深究Java虚拟机
JVM:Java Virtual Machine Java虚拟机

JRE:Java Runtime Environment Java运行时环境

ABI:Application Binary Interface 应用二进制接口是一个程序在运行时应用的环境也是一种可执行文件的格式操作系统都有自己的进程地址控件硬件系统也各不相同java在所有的计算机上都使用相同的ABI

java运行时环境JRE包括java虚拟机是java ABI与各种硬件/操作系统ABI之间的桥梁

)java源代码编译后生成的目标代码是一种字节码(bytecode)与其他语言不同的是java的字节码是一种中立结构的机器代码(不是任何现有系统上的二进制指令代码)通过JVM可以快速地解释并运行在任何特定的计算机上

)java程序的执行通过JVM实现

)一般情况下JVM是在运行java程序时调用的

)JVM读取字节码程序解释或翻译成实际的机器指令后再执行实行了java的一次编写多处运行的特点

Java虚拟机是什么

Java虚拟机之所以称为虚拟就是因为它仅仅是由一个规范来定义的抽象计算机要运行某个Java程序首先需要一个符合该规范的具体实现

下面主要讨论这个规范本身

要理解Java虚拟机你必须意识到当你说Java虚拟机可能指的是如下三种不同的东西

<![if !supportLists]>· 抽象规范

<![if !supportLists]>· <![endif]>一个具体的实现

· 一个运行中的虚拟机实例

Java虚拟机抽象规范仅仅是个概念该规范的具体实现可能来自多个提供商并存在多个平台上它或者完全用软件实现或者以硬件和软件相结合的方式来实现当运行一个Java程序的同时也就在运行了一个Java虚拟机实例

对JVM规范的抽象说明是一些概念的集合它们已经在书《The Java Virtual Machine Specification》(《Java虚拟机规范》)中被详细地描述了对JVM的具体实现要么是软件要么是软件和硬件的组合它已经被许多生产厂商所实现并存在于多种平台之上运行Java程序的任务由JVM的运行期实例单个承担在本文中我们所讨论的Java虚拟机(JVM)主要针对第三种情况而言它可以被看成一个想象中的机器在实际的计算机上通过软件模拟来实现有自己想象中的硬件如处理器堆栈寄存器等还有自己相应的指令系统

JVM在它的生存周期中有一个明确的任务那就是运行Java程序因此当Java程序启动的时候就产生JVM的一个实例当程序运行结束的时候该实例也跟着消失了下面我们从JVM的体系结构和它的运行过程这两个方面来对它进行比较深入的研究

<![if !supportLineBreakNewLine]>

<![endif]>

Java虚拟机的生命周期

一个运行时的Java虚拟机实例的天职就是负责运行一个Java程序当启动一个Java程序时一个虚拟机实例也就诞生了当该程序关闭退出这个虚拟机实例也就随之消亡每个Java程序都运行在于自己的Java虚拟机实例中Java虚拟机实例通过调用某个初始类的main()方法来运行一个Java程序而这个main()方法必须是publicstatic返回值为voidmain()方法作为该程序初始线程的起点任何其他的线程都是由这个初始线程启动的

Java虚拟机内部有两种线程守护线程和非守护线程

守护线程通常由虚拟机自己使用的比如执行垃圾收集任务的线程但是Java程序也可以把它的创建的任何线程标记为守护线程

而Java程序中的初始线程就是开始于main()的那个是非守护线程只要有非守护线程在运行那么这个Java程序也在继续运行只有该程序中所有的非守护线程都终止时虚拟机实例将自动退出

Java虚拟机的体系结构

Java虚拟机的结构分为类装载子系统执行引擎运行时数据区本地方法接口

其中运行时数据区又分为方法区JavaPC寄存器本地方法栈

)thiswidth=; border= twffan=done>

Java虚拟机结构图

Java虚拟机由五个部分组成:一组指令集一组寄存器一个栈一个无用单元收集堆(Garbagecollectedheap)一个方法区域这五部分是Java虚拟机的逻辑成份不依赖任何实现技术或组织方式但它们的功能必须在真实机器上以某种方式实现

类装载子系统(class loader)

Java虚拟机中负责查找并装载类型的那部分称为类装载子系统

Java虚拟机有两种类装载器启动类装载器和用户自定义类装载器

启动类装载器是Java虚拟机实现的一部分

用户自定义类装载器是Java程序的一部分

类装载器的动作

<![if !supportLists]> <![endif]>装载查找并装载类型的二进制数据

<![if !supportLists]> <![endif]>连接执行验证准备以及解析(可选)

验证确保被导入类型的正确性

准备为类变量分配内存并将其初始化为默认值

把类型中的符号引用换为直接引用

<![if !supportLists]> <![endif]>初始化把类变量初始化为正确的初始值

执行引擎

处于JVM的核心位置在Java虚拟机规范中它的行为是由指令集所决定的尽管对于每条指令规范很详细地说明了当JVM执行字节码遇到指令时它的实现应该做什么但对于怎么做却言之甚少Java虚拟机支持大约个字节码每个字节码执行一种基本的CPU运算例如把一个整数加到寄存器子程序转移等Java指令集相当于Java程序的汇编语言

由于指令系统的简单性使得虚拟机执行的过程十分简单从而有利于提高执行的效率指令中操作数的数量和大小是由操作符决定的如果操作数比一个字节大那么它存储的顺序是高位字节优先例如一个位的参数存放时占用两个字节其值为: 第一个字节*+第二个字节字节码

指令流一般只是字节对齐的指令tableswitch和lookup是例外在这两条指令内部要求强制的字节边界对齐

Java指令集

Java虚拟机支持大约个字节码每个字节码执行一种基本的CPU运算例如把一个整数加到寄存器子程序转移等Java指令集相当于Java程序的汇编语言

Java指令集中的指令包含一个单字节的操作符用于指定要执行的操作还有个或多个操作数提供操作所需的参数或数据许多指令没有操作数仅由一个单字节的操作符构成

虚拟机的内层循环的执行过程如下:

do{

取一个操作符字节;

根据操作符的值执行一个动作;

}while(程序未结束)

由于指令系统的简单性使得虚拟机执行的过程十分简单从而有利于提高执行的效率指令中操作数的数量和大小是由操作符决定的如果操作数比一个字节大那么它存储的顺序是高位字节优先例如一个位的参数存放时占用两个字节其值为:

第一个字节*+第二个字节字节码指令流一般只是字节对齐的指令tableswitch和lookup是例外在这两条指令内部要求强制的字节边界对齐

运行时数据区

方法区

在Java虚拟机中被装载类型的信息存储在一个逻辑上被称为方法区的内存中当虚拟机装载某个类型时它使用类装载器定位相应的class文件然后读入这个class文件然后将它传输到虚拟机中紧接着虚拟机提取其中的类型信息并将这些信息存储到方法区该类型中的类(静态)变量同样也是存储在方法区中方法区的大小不必固定可以根据需要动态调整方法区也可以被垃圾收集因为虚拟机允许通过用户定义的类装载器来动态扩展Java程序因此一些类也会成为不再引用的类

对于每个装载的类型虚拟机都会在方法区中存储以下类型信息

<![if !supportLists]>· <![endif]>这个类型的全限定名

<![if !supportLists]>· <![endif]>这个类型的直接超类的全限定名(除非是javalangObject无超类)

<![if !supportLists]>· <![endif]>这个类型是类类型还是接口类型

<![if !supportLists]>· <![endif]>这个类型的访问修饰符(publicabstract )

<![if !supportLists]>· <![endif]>任何直接超接口的全限定名的有序列表

除了上面列出的基本类型信息外虚拟机还为每个被装载的类型存储以下信息

<![if !supportLists]>· <![endif]>该类型的常量池

<![if !supportLists]>· <![endif]>字段信息

<![if !supportLists]>· <![endif]>方法信息

<![if !supportLists]>· <![endif]>除了常量以外所有类(静态)变量

<![if !supportLists]>· <![endif]>一个到类ClassLoader的引用

<![if !supportLists]>· <![endif]>一个到Class类的引用

方法区与传统语言中的编译后代码或是Unix进程中的正文段类似它保存方法代码(编译后的java代码)和符号表在当前的Java实现中方法代码不包括在无用单元收集堆中但计划在将来的版本中实现每个类文件包含了一个Java类或一个Java界面的编译后的代码可以说类文件是Java语言的执行代码文件为了保证类文件的平台无关性Java虚拟机规范中对类文件的格式也作了详细的说明其具体细节请参考Sun公司的Java虚拟机规范

无用单元收集堆

Java的堆是一个运行时数据区类的实例(对象)从中分配空间Java语言具有无用单元收集能力:它不给程序员显式释放对象的能力Java不规定具体使用的无用单元收集算法可以根据系统的需求使用各种各样的算法

无用单元收集堆(Garbagecollectedheap)

Java虚拟机的栈有三个区域:局部变量区运行环境区操作数区

()局部变量区每个Java方法使用一个固定大小的局部变量集它们按照与vars寄存器的字偏移量来寻址局部变量都是位的长整数和双精度浮点数占据了两个局部变量的空间却按照第一个局部变量的索引来寻址(例如一个具有索引n的局部变量如果是一个双精度浮点数那么它实际占据了索引n和n+所代表的存储空间)虚拟机规范并不要求在局部变量中的位的值是位对齐的虚拟机提供了把局部变量中的值装载到操作数栈的指令也提供了把操作数栈中的值写入局部变量的指令

()运行环境区在运行环境中包含的信息用于动态链接正常的方法返回以及异常传播

·动态链接

运行环境包括对指向当前类和当前方法的解释器符号表的指针用于支持方法代码的动态链接方法的class文件代码在引用要调用的方法和要访问的变量时使用符号动态链接把符号形式的方法调用翻译成实际方法调用装载必要的类以解释还没有定义的符号并把变量访问翻译成与这些变量运行时的存储结构相应的偏移地址动态链接方法和变量使得方法中使用的其它类的变化不会影响到本程序的代码

·正常的方法返回

如果当前方法正常地结束了在执行了一条具有正确类型的返回指令时调用的方法会得到一个返回值执行环境在正常返回的情况下用于恢复调用者的寄存器并把调用者的程序计数器增加一个恰当的数值以跳过已执行过的方法调用指令然后在调用者的执行环境中继续执行下去

·异常和错误传播

异常情况在Java中被称作Error(错误)或Exception(异常)是Throwable类的子类在程序中的原因是:①动态链接错如无法找到所需的class文件②运行时错如对一个空指针的引用

·程序使用了throw语句

当异常发生时Java虚拟机采取如下措施:

·检查与当前方法相联系的catch子句表每个catch子句包含其有效指令范围能够处理的异常类型以及处理异常的代码块地址

·与异常相匹配的catch子句应该符合下面的条件:造成异常的指令在其指令范围之内发生的异常类型是其能处理的异常类型的子类型如果找到了匹配的catch子句那么系统转移到指定的异常处理块处执行;如果没有找到异常处理块重复寻找匹配的catch子句的过程直到当前方法的所有嵌套的catch子句都被检查过

·由于虚拟机从第一个匹配的catch子句处继续执行所以catch子句表中的顺序是很重要的因为Java代码是结构化的因此总可以把某个方法的所有的异常处理器都按序排列到一个表中对任意可能的程序计数器的值都可以用线性的顺序找到合适的异常处理块以处理在该程序计数器值下发生的异常情况

·如果找不到匹配的catch子句那么当前方法得到一个未截获异常的结果并返回到当前方法的调用者好像异常刚刚在其调用者中发生一样如果在调用者中仍然没有找到相应的异常处理块那么这种错误传播将被继续下去如果错误被传播到最顶层那么系统将调用一个缺省的异常处理块

()操作数栈区机器指令只从操作数栈中取操作数对它们进行操作并把结果返回到栈中选择栈结构的原因是:在只有少量寄存器或非通用寄存器的机器(如Intel)上也能够高效地模拟虚拟机的行为操作数栈是位的它用于给方法传递参数并从方法接收结果也用于支持操作的参数并保存操作的结果例如iadd指令将两个整数相加相加的两个整数应该是操作数栈顶的两个字这两个字是由先前的指令压进堆栈的这两个整数将从堆栈弹出相加并把结果压回到操作数栈中

每个原始数据类型都有专门的指令对它们进行必须的操作每个操作数在栈中需要一个存储位置除了long和double型它们需要两个位置操作数只能被适用于其类型的操作符所操作例如压入两个int类型的数如果把它们当作是一个long类型的数则是非法的在Sun的虚拟机实现中这个限制由字节码验证器强制实行但是有少数操作(操作符dupe和swap)用于对运行时数据区进行操作时是不考虑类型的

寄存器

Java虚拟机的寄存器用于保存机器的运行状态与微处理器中的某些专用寄存器类似

Java虚拟机的寄存器有四种:

pc:Java程序计数器

optop:指向操作数栈顶端的指针

frame:指向当前执行方法的执行环境的指针

vars:指向当前执行方法的局部变量区第一个变量的指针

Java虚拟机是栈式的它不定义或使用寄存器来传递或接受参数其目的是为了保证指令集的简洁性和实现时的高效性(特别是对于寄存器数目不多的处理器)

所有寄存器都是位的

本地方法接口

Java应用程序设计接口

Java Application Programming Interface简称Java API其中文名为Java应用程序设计接口它是一个软件集合其中有许多开发时所需要的控件可以用它来辅助开发

Java API和JVM构成了Java运行的基本环境这两种软件整合在一起处于计算机之上通过这两种软件Java平台把一个Java应用程序从硬件系统分离开从而很好地保证了程序的独立性为了更好地适应开发的需要Java的设计者们提供了种版本的Java平台Java Micro Edition (JME )Java Standard Edition(JSE)和 Java Enterprise Edition (JEE)每一种版本都提供了丰富的开发工具以适应不同的开发需要

Java虚拟机的运行过程

上面对虚拟机的各个部分进行了比较详细的说明下面通过一个具体的例子来分析它的运行过程

虚拟机通过调用某个指定类的方法main启动传递给main一个字符串数组参数使指定的类被装载同时链接该类所使用的其它的类型并且初始化它们例如对于程序

width=% border=>

class HelloApp

{

public static void main(String[] args)

{

Systemoutprintln(Hello World!);

for (int i = ; i < argslength; i++ )

{

Systemoutprintln(args[i]);

}

}

}

编译后在命令行模式下键入 java HelloApp run virtual machine

将通过调用HelloApp的方法main来启动java虚拟机传递给main一个包含三个字符串runvirtualmachine的数组现在我们略述虚拟机在执行HelloApp时可能采取的步骤

开始试图执行类HelloApp的main方法发现该类并没有被装载也就是说虚拟机当前不包含该类的二进制代表于是虚拟机使用ClassLoader试图寻找这样的二进制代表如果这个进程失败则抛出一个异常类被装载后同时在main方法被调用之前必须对类HelloApp与其它类型进行链接然后初始化链接包含三个阶段检验准备和解析检验检查被装载的主类的符号和语义准备则创建类或接口的静态域以及把这些域初始化为标准的默认值解析负责检查主类对其它类或接口的符号引用在这一步它是可选的类的初始化是对类中声明的静态初始化函数和静态域的初始化构造方法的执行一个类在初始化之前它的父类必须被初始化整个过程如下

width=% border=>

http://imgeducitycn/img_///jpg onload=javascript:if(thiswidth>)thiswidth=; border= twffan=done>

<![endif]>

虚拟机的运行过程

结束语

本文通过对JVM的体系结构的深入研究以及一个Java程序执行时虚拟机的运行过程的详细分析意在剖析清楚Java虚拟机的机理

http://imgeducitycn/img_///gif onload=javascript:if(thiswidth>)thiswidth=; border= twffan=done>

<![endif]>

我们可以通过helloworld来理解这几个缩写词的具体含义

public class HelloWorld {

public static void main(String[] args) {

Systemoutprintln(helloworld);

}

}

编译之后 我们得到了HelloWorldclass(图中的Your programs class files

在HelloWorld里面 我们调用了 JAVA API中的 javalangSystem这个类的静态成员对象 out out 的静态方法: public static void println(String string);

然后我们让虚拟机器来执行这个HelloWorld

虚拟机会在classpath中找到HelloWorldclass

虚拟机中的解释器(interpret)会把HelloWorldclass解释成字节码

把解释后的字节码交由execution engin执行

execution engin会调用native method(即平台相关的字节码)来在host system的stdout(显示器)的指定部分打印出指定的字符串

这样 我们就看到helloworld字样了

有了这个流程后 我们就好理解上面几个术语了

a JDK: java develop kit (JAVA API包)

b SDK: software develop kit 以前JDK 叫做java software develop kit 后来出了版本后 就改名叫jdk了 省时省力 节约成本

c JRE java runtime environment 我们的helloworld必须在JRE(JAVA运行环境JAVA运行环境又叫JAVA平台)里面 才能跑起来 所以 显然地 JREJRE顾名思义只是java class运行时需要的环境JDK不仅包含了JRE还提供了开发调试java程序需要的工具

d JVM java virtual machine 简单地讲 就是把class文件变成字节码 然后送到excution engin中执行 而为什么叫虚拟机 而不叫真实机呢? 因为JVM本身是又不能运算 又不能让显示器显示helloworld 它只能再调用host system的API 比如在w里面就会调c++的API 来让CPU帮他做做算术运算 来调用c++里面的API来控制显示器显示显示字符串 而这些API不是JDK里面有的我们平时又看不见的所以我们就叫它native api了(亦曰私房XX)

<![if !supportLineBreakNewLine]>

<![endif]>

总结

Java平台-虚拟机

Java的核心技术JVM(Java Virtual Machine)是Java实现平台无关性的基础

Java虚拟机(JVM)是可运行Java代码的假想计算机

只要根据JVM规格说明把解释器移植到特定的计算机上就能保证经过编译的任何Java代码能够在该系统上运行

Java平台-虚拟机工作原理

Java编译程序把Java源程序翻译为JVM可执行代码-字节码

Java编译器不把变量和方法的引用编译为数值引用也不确定程序执行过程中的内存布局而是把这些符号引用

信息存储在字节码中由解释器在运行过程中建立内存布局然后再通过查表来确定一个方法所在的地址

这样保证了Java的可移植性和安全性

运行JVM字节码的工作由解释器来完成这包括三部分

– 代码装入该工作由类装载器(class loader)完成

– 代码校验被装入代码经过字节码校验器进行检查类只检查一次无需反复校验效率高

– 代码执行通过校验开始执行代码有下列方式

解释 执行方式(笔译)

即时 编译方式(口译)

               

上一篇:用Solstice Enterprise Manager建立Java网络管理应用程序

下一篇:测试 Java 类的非公有成员变量和方法