java虚拟机

来源:互联网 发布:易语言a星寻路算法 编辑:程序博客网 时间:2024/06/02 04:01

一.java虚拟机是什么

可能指以下三种不同的东西

   1.抽象规范 仅仅是一个概念,规范的具体实现可能来自多个提供商,并存与多个平台上

   2.一个具体实现

   3.一个运行中的虚拟机实例

 

二.java虚拟机的生命周期

   当启动一个java程序时,一个虚拟机的实例就诞生了.关闭则虚拟机实例消亡.一台机器运行多个java程序,将产生多个虚拟机实例.每个java程序都在他自己的实例中运行.

   java虚拟机调用main()方法来运行一个java程序.main()必须是public,static,void,并接受一个字符串作为参数.任何满足这个条件的类都可以作为java程序运行的起点.

   java线程分为守护线程和非守护线程.垃圾收集任务的线程属于守护线程.java程序中的初始线程开始于main()的线程,是非守护线程.只要有任何非守护线程运行,那么这个java程序也继续运行.当该程序中所有的非守护线程终止时,虚拟机实例自动退出。假如安全管理器允许,程序本身也可以调用Runtime类或者System类的exit()方法来退出。

 

三.虚拟机的体系结构

 如下图所示:

每个java虚拟机实例都有一个方法区以及一个堆,它们是由该虚拟机实例中所有线程共享。当虚拟机装载class文件时,它会从这个class文件中读取二进制数据并解析类型信息。然后,它把这些信息放到方法区中。当程序运行时,虚拟机把所有该程序运行时产生的对象都放进堆中。

当每一个线程被创建时,它都将得到它自己的PC寄存器以及一个java栈。如果线程正在执行一个java方法(非本地方法),那么PC寄存器的值总是指示下一条将被执行的指令,而它的java栈总是存储该线程中java方法调用的状态-----包括它的局部变量,被调用时传过来的参数,它的返回值以及运算的中间结果等等。本地方法调用的状态,则是以某种依赖于具体实现的方式存储在本地方法栈中,也可能是在寄存器或者其他某些与特定实例相关的内存区中。

 

Java栈:

       由许多栈帧(stack frame)或者说帧(frame)组成的,一个栈帧包含一个Java方法调用的状态。当线程调用一个方法时,虚拟机压入一个新的栈帧到该线程的Java栈种;当该方法返回时,这个栈帧被从Java栈中弹出并抛弃。

 

1.数据类型

   基本类型:

byte   8bit,带符号,二进制补码(-2的7次方到2的7次方-1,包括两端的值在内)

short  16bit,带符号,二进制补码(-2的15次方到2的15次方-1,包括两端的值在内)

int      32bit,带符号,二进制补码(-2的31次方到2的31次方-1,包括两端的值在内)

long   64bit,带符号,二进制补码(-2的63次方到2的63次方-1,包括两端的值在内)

float   32bit,IEEE754标准单精度浮点数

double 64bit,IEEE754标准双精度浮点数

boolean(编译后用int或者byte代表boolean,0代表FALSE,其它代表TRUE)

char 持有变量原始值

   引用类型:类类型,接口类型,数组类型,null(表示该变量没有引用任何对象) 持有变量引用值

   Java中还有一个只在内部使用的基本类型 returnAddress,Java程序员不能使用这个类型,这个基本类型被用来实现Java程序中的finally子句。(同一方法中某操作码的地址)

 

2.字长考量

   虚拟机实现者至少选择32位作为一个字长。

 

3.类装载子系统

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

   Java虚拟机有两种类装载器:

          启动类装载器:JVM实现的一部分

          用户自定义类装载器:java程序的一部分,是普通的java对象,它派生自java.lang.ClassLoader。ClassLoader中定义的方法为程序提供了装载类机制的接口。每一个被装载的类,虚拟机都为它创建一个Class实例来代表该类型。类装载器和Class实例都放在堆区,装载的类型信息都放在方法区中。

   不同的类装载器装载的类被放在java虚拟机内不同的命名空间中。类装载器子系统除了要定位和导入二进制class文件,还必须验证被导入类的正确性,为变量分配并初始化内存,以及帮助解析符号引用。严格按照以下顺序动作:

     ●装载:查找并装载类型的二进制数据

     ●连接:执行验证,准备以及解析

     ●初始化:把类变量初始化为正确的值

 

4.方法区

   Java虚拟机中,关于被装载的类型信息放在一个逻辑上被成为方法区的内存中。当虚拟机运行Java程序时,它会查找使用存储在方法区的类型信息。由于所有线程都是共享的,所以它们对方法区的数据访问必须设计为线程安全的。方法区大小不必是固定的,虚拟机可根据实际需要动态调整。也不必是连续的,方法区可以在一个堆中自由分配。虚拟机也可以允许用户或者程序员制定方法区的初始大小以及最小和最大尺寸等。方法区可以被垃圾回收。

  方法区存储的类型信息:

   ●这个类型的全限定名

   ●这个类型的直接超类的全限定名(除非这个类型是java.lang.Object,它没有超类)。

   ●这个类型是类类型还是接口类型

   ●这个类型的访问修饰符

   ●任何直接超接口的全限定名的有序列表

   ●该类型的常量池:该类型的所有常量的一个有序结合,包括直接常量和对其他类型、字段和方法的符号引用

   ●字段信息:要保存字段名,字段类型,字段修饰符以及字段在接口或类中声明的顺序

   ●方法信息:要保存方法名,方法的返回类型,方法参数的类型和数量,方法的修饰符以及声明的顺序。如果方法不是抽象活本地的方法,还得保存方法的字节码,操作数栈和方法的栈帧种的局部变量区的大小,异常表。

   ●除了常量以外的所有类(静态)变量:编译时常量(那些final声明以及用编译时已知的值初始化的类变量)??

   ●一个到类ClassLoader的引用

   ●一个到Class的引用

   ●方法表:为了尽可能提高访问效率,设计者必须仔细设计存储在方法区中的类型信息的数据结构 ,因此除了以上原始类型信息以外,还可能存在快速访问原始类型信息的数据结构,比如方法表。虚拟机对每个装载的非抽象类都生成一个方法表,方法表是一个数组,它的元素就是所有它的实例可能被调用的实例方法的引用,包括那些超类继承的实例方法。接口和抽象类没有方法表,因为不会生成实例。

              方法表是一个指针数组,其中的每一项都是一个指向“实例方法数据”的指针,实例方法可以被那类的对象吊用。方法表指向的实例方法数据包括以下的数据:

        此方法的操作数栈和局部变量的大小

        此方法的字节码

        异常表

 

5.堆:Java程序在运行时所创建的所有类实例或数组都放在同一个堆中。某些实现允许用户或程序员制定堆的初始大小、最大最小值等等。堆的内部表示:堆除了包括本身对象的数据外还包括要实现锁所需要的数据以及实现等待集合的数据相关联。锁是用来实现多个线程对共享数据的互斥访问的,等待集合时让多个线程为了完成一个共同的目标而协调工作的。最后一种是与垃圾收集器相关的数据

   数组的内部表示:堆中的每个数组对象还必须保存数组的长度,数组数据,以及某些指向数组的类数据的引用。

 

6.程序计数器

   Java程序,每一个线程都有自己的PC寄存器,它是在线程启动时创建的。大小为一个字长。线程执行某个方法的时候,PC寄存器总是指向下一条将要被执行的指令的“地址”,“地址”可以是本地指针,或者方法字节码中相对于该方法起始指令的偏移量。如果线程执行本地方法,PC寄存器的值是“undefined”

 

7.Java栈

   每启动一个线程,Java虚拟机都会为它分配一个栈。虚拟机只会对栈进行两种操作:以帧为单位的压栈或出栈。

   当线程调用一个方法的时候,虚拟机都会在该线程的Java栈中压入一个新帧。当执行这个方法时,它使用这个帧来存储参数,局部变量,中间运算结果等等数据。

    Java方法有两种方式返回。一种return,正常返回。一种抛异常终止。不管哪种返回当前帧都会弹出并释放。上一个方法就成为当前帧。Java栈上所有数据都是线程私有的。不需要同步。某些实现可以允许指定Java栈的初始大小和最大最小值。

 

8.栈帧

   三部分组成:局部变量区,操作数栈和帧数据区。局部变量区和操作数栈的大小是视方法而定的。它们是按字长计算的。编译器在编译时就确定了这些值并放在Class文件中。而帧数据区的大小则依赖于具体的实现。

   局部变量区:被组织成一个以字长为单位、从0开始计数的数组。字节码指令通过从0开始的索引使用其中的数据。类型为int,float,reference和returnAddress的值在其中占据一项,而double,long则占据连续的两项。byte,char,short存入数组前都被转换为int值。访问long和double的时候,指令只需要指出第一项索引的值就可以了。虚拟机是不直接支持boolean的,编译器用int值来表示boolean。但byte,short,char,java虚拟机是直接支持的,这些类型的值可以作为实例变量或者数组元素存储在局部变量区,也可以作为类变量存储在方法区中。但在局部变量区和操作数栈中都会转换成int值,它在栈中是当作int来处理的,只有当它被存回堆或者方法区中时,才转换成原来的类型。

   操作数栈:和局部变量区一样,被组织成一个以字长为单位的数组。但不是用索引来访问的,是通过标准的栈操作来访问的,压栈,出栈。虚拟机在操作数栈中存储数据的方式和局部变量区一样。byte,short和char入栈之前都被转换成int类型。Java虚拟机没有寄存器,程序计数器也无法被程序指令直接访问。Java虚拟机的指令是从操作数栈中而不是从寄存器中取得操作数的,因此它的运作方式是基于栈的,而不是基于寄存器的。

    帧数据区:除了局部变量区和操作数栈外,Java栈帧还需要一些数据来支持常量池解析、正常方法返回,异常派发机制。这些信息都保存在帧数据区中。

 

9.本地方法栈

   当某个线程调用本地方法时,它就进入了一个全新的并且不受虚拟机限制的世界。本地方法栈占用的内存不必是固定的大小,它可以根据需要动态的扩展或者收缩。某些实现也允许用户或程序员指定本地方法栈的初始大小,最大,最小值等。

 

10.执行引擎

    任何java虚拟机实现的核心都是它的执行引擎。在java虚拟机规范中,执行引擎的行为使用指令集来定义。对于每条指令,规范详细定义了当实现执行到该指令时应该处理什么,但是对如何处理却言之甚少。实现的设计者有权决定如何执行字节码,实现可以采取解释、即时编译或直接使用芯片上的指令执行,还可以是它们的混合,或者你能想的其他任何新技术。

    运行中Java程序的每一个线程都是一个独立的虚拟机执行引擎的实例。

    字节码在class文件中是以字节对齐的。

    指令集:方法的字节码流是Java虚拟机指令序列构成的。每一条指令包含一个单字节的操作码,后面跟随0个或多个操作数。

    线程:Java虚拟机规范定义了线程模型,这个模型的目标是要有助于在很多体系结构上都实现它。任何Java虚拟机的线程实现都必须支持同步的两个方面:对象锁,线程等待通知。

Java虚拟机规范中,Java线程的行为是通过术语----变量、主存和工作内存---来定义的。每一个java虚拟机实例都有一个主存,用于保存所有的程序变量(对象的实例变量、数组的元素以及类变量)。每一个线程都有一个工作内存,线程用它保存所使用和赋值的变量的“工作拷贝”。局部变量和参数,因为它们是每个线程私有的,可以从逻辑上看成是工作内存或主存的一部分。

 

11.本地方法接口

    并不强求Java虚拟机实现支持任何特定的本地方法接口。

原创粉丝点击