Java虚拟机的生命周期
一个运行时的Java虚拟机负责运行一个Java程序 。Java虚拟机的主要任务是加载class文件并且执行其中的字节码。Java 虚拟机包含一个类装载器(class loader)。它可以从程序和API中加载class文件。 Java API中只有程序执行时需要的部些类才会被装载 。 当启动一个Java程序时,一个虚拟机实例也就诞生了. 当该程序关闭退出, 这个虚拟机实例也就随之消亡 。 如果在同一 台计算机上同时运行三个Java程序, 将得到三个Java虚拟机实例。 每个lava程序都运行于它自已的Java虚拟机实例中 。
Java虚拟机实例通过调用某个初始类的main( )方法运行一个Java程序。而这个main( ) 方法必须是公有的(public)、静态的(static),通回值为void,井且接受一个字符中数组作为参数。住何拥有这样一个main( )方法的类都可以作为Java程序运行的起点。
虚拟机体系结构
在Java虚拟机规范中, 一个虚拟机实例的行为是分别按照子系统、 内存区、 数据类型以及指令这几个术语来描述的。 这些组成部分一起展示了抽象的虚拟机的内部抽象体系结构 。 但是规范中对它们的定义并非是要强制规定Java虚拟机实现内部的体系结构, 更多的是为了严格地定又这些实现的外部特征 。 规范本身通过定义这些抽象的组成部分以及它们之间的交互, 来定义任何Java虚拟机实现都必须遵守的行为 。
Java虚拟机的体系结构如下, 包括在规范中描述的主要子系统和内存区。
执行引擎
每个Java虚拟机都有一个执行引擎, 它负责执行那些包含在被装载类的方法中的指令。不同的Java虚拟机中, 执行引擎可能不同。 在由软件实现的虚拟机中, 最简单的执行引擎就是一次性解释字节码。还有一种执行更快的引擎, 但是也更消耗内存, 叫做"即时编译器"(just-in-time compiler)。在这种情况下,第一次被执行的字节码会被编译成本地机器代码。 编译出的本地机器代码会被缓存, 当方法以后被调用的时候可以重用。 第三种执行引擎是自适应优化器。在这种方法里,虚拟机开始的时候解释字节码,但是会监视运行中程序的活动,并且记录下使用最频繁的代码段。 程序运行的时像 , 虚拟机只把那些活动最频繁的代码编译成地代码, 其他的代码由于使用得并不很频繁, 继续保留为字节码一由虚期机继续解释它们。一个自适应的优化器可以使得Java虚拟机在80%-90%的时间里城行被优化过的本地代码, 而只需要编译10%-20%对性能有影响的代码。最后一种虚拟机由硬件芯片构成, 它用本地方法执行Java字节码, 这种执行引擎实际上是内嵌在芯片里的 。
本地方法接口
当Java虚拟机是由主机操作系统上的软件实现的时候, Java程序通过调用本地方法( native method)和主机交互。 Java中有两种方法: Java方法和本地方法。Java方法是由Java话言编写,编译成字节码,存储在class文件中的。本地方法是由其他语言(比如c, c++,或者汇编语言) 编写的, 编译成和处理器相关的机器代码 。本地方法保存在动态连接库中, 格式是各个平合专有的。Java方法是与平台无关的,但是本地方法却不是。运行中的Java程序调用本地方法时,虚拟机包含这个本地方法的动态库. 并调用这个方法。 在图1-4中可以看到, 本地方法是联系!ava程序和底层主机操作系统的進接方法
通过本地方法, Java程序可以直接访同底层操作系统的资源。如果你这样用,你的程序就变成了平台相关的, 因为包含本地方法的动态库是平台相关的。除此之外, 使用本地方法还可能把程序变得和特定的Java平台实现相关。一个本地方法接口——Java本地接口 ( Java Native lnterface, JNI)——使得本地方法可以在特定主机系统的任何一个Java平台实现上运行。然而Java平合供应商并不一定必须支持JNI。除了lNl之外, 他们还可以提供自己的本地方法控口。
Java给人们提供了选择的机会。 如果希望使用特定主机上的资源, 它们又无法从Java API访问, 那么可以写一个平台相关的Java程序来调用本地方法。 如果希望保证程序的平台无关性, 那么只能通过Java API访问问底层系统资源。
类加载器的体系结构
每个Java虚拟机都有一个类装载器子系统,它根据给定的全限定名来装入类型(类或接口)。类装载器子系统包含多个类加载器,类加载器的体系结的是Java虚拟机在安全性和网络移动性上发挥重要作用的。Java虚拟机拥有灵活的类装载器体系结构, 从而使Java应用程序得以用自定义的方式来实现类的装载。
运行时数据区
当Java虚拟机运行一个程序时,它需要内存来存储许多东西,例如,字节码,从已装载的Class文件中得到的其他信息,程序创建的对象,传递给方法的参数,返回值,局部変量以及运算的中间结果等等。 Java虚拟机把这些东西都组织到几个“运行时数据区“ 中以便于管理。
尽管这些“运行时数据区”都会以某种形式存在于每一个Java虚拟机实现中, 但是规范对它们的描述却是相当抽象的。 这些运行时数据区结构上的细节,大多数都由具体实现的设计者决定。
不同的虚拟机实现可能具有很不同的内存限制, 有的实现可能有大量的内存可用, 有的可能只有很少。有的实现可以利用虚拟内存,有的则不能。规范本身对“运行时数据区”只有抽象的描述, 这就使得Java虚拟机可以很容易地在各种计算机和设备上实现 。
某些运行时数据区是由程序中所有线程共享的, 还有一些则只能由一个线程拥有。 每个Java 虚拟机实例都有一个方法区以及一个堆, 它们是由该虚拟机实例中所有线程共享的. 当虚拟机装载一个class文件时,它会从这个class文件包含的二进制数据中解析装型信息。然后,它把这些类型信息放到到方法区中。 当程序运行时,虚拟机会把所有该程序在运行时创建的对象都放到堆中。
当每一个新整程被创建时它都将得到它自己的PC寄存器(程序计数器)以及一个Java栈。如果线程正在执行的是一个Java方法(非本地方法).那么PC寄存器的值将总是指示下一条將被执行的指令, 而它的Java栈则总是存储该线程中 java方法调用的状态——包括它的局部变量,被调用时传进来的参数,它的返回值,以及运算的中间结果等等。而本地方法调用的状态,则是以某种依赖于具体实现的方式存储在本地方法栈中, 也可能是在寄存器或者其他某些与特定实现相关的内存区中。
Java栈是由许多栈帧( stack frame)或者说帧( frame)组成的, 一个栈帧包含一个Java方法调用的状态。当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中;当该方法返回时, 这个栈帧被从Java栈中弹出并地弃。
Java虚拟机没有寄存器, 其指令集使用java栈来存储中间数据。 这样设计的原因是为了保持Java虚拟机的指令集尽量紧凑、同时也便于Java虚拟机在那些只有很少通用寄存器的平台上实现。另外,Java虚拟机的这种基于栈的体系结构, 也有助于运行时某些虚拟机实现的动态编译器和即时编译器的代码优化。
Java虚拟机会为每一个线程创建内存区, 这些内存区域是私有的 , 任何线程都不能访问另一个线程的 PC寄存器或者Java栈。对于一个正在运行Java方法的线程而言, 它的Pc寄存器总是指向下一条将被执行的指令