每个Java程序都运行于某个具体的Java虚拟机实现的实例上。
当启动一个Java程序时,一个虚拟机实例就诞生了,当该程序关闭退出,这个虚拟机实例就随之消亡。如果同一台计算机上同时运行3个Java程序,将得到3个Java虚拟机实例。每个程序都运行于它自己的Java虚拟机实例中。
Java虚拟机实例通过调用某个初始类的main方法来运行一个Java程序。这个main方法必须是public,static,接受String[]作为参数,返回void。任何拥有这样一个main方法的类都可以作为Java程序运行的起点。
class Test{
public static void main(String[] args){
//
}
}
告诉Java虚拟机要运行的Java程序中初始类的名字后,整个程序将从它的main方法开始执行。
java Test Hello world
则args[0]="Hello",args[1]="world"
main方法是程序初始线程的起点,任何其他线程都是由这个初始线程启动的。
Java虚拟机内部有两种线程,非守护线程和守护线程。
从main开始的初始线程是非守护线程,只要还有任何非守护线程在运行,这个Java程序就继续运行。当程序中所有的非守护线程都终止时,虚拟机实例将自动退出。
守护线程通常是由虚拟机自己使用,比如执行垃圾收集任务的线程。Java程序也可以把它创建的任何线程标记为守护线程。
Java虚拟机体系结构由4部分构成:子系统,内存区,数据类型,指令。
每个Java虚拟机都有一个类装载器子系统,它根据给定的全限定名来装入类型。
每个Java虚拟机都有一个执行引擎,它负责执行那些包含在被装载的方法中的指令。
某些运行时数据是由程序中的所有线程共享的,还有一些则只能由一个线程拥有。
方法区和堆,是由该虚拟机实例中所有线程共享的。
虚拟机会从装载的二进制文件中解析类型信息放到方法区中,把该程序运行时创建的对象都放到堆中。
每一个新线程被创建时,都将得到它自己的程序指针寄存器(PC)以及一个Java栈。
如果线程正在执行的是一个Java方法(非本地方法),那么PC指向下一条被执行的指令,而Java栈总是存储该线程中Java方法的调用状态。
本地方法调用状态,则是以某种依赖于具体实现的方式存储在本地方法栈中,也可能是寄存器或者其他特定实现相关的内存区中。
Java栈由许多帧(frame)组成,帧中包含了Java方法的调用状态。当线程调用一个Java方法时,虚拟机压入一个新的frame到该线程的Java栈中,该方法返回时,这个frame从Java栈中弹出并抛弃。
Java虚拟机为每一个线程创建的内存区是私有的,任何线程都不能访问其他线程的PC或Java栈。
线程1,2正在执行Java方法,它们的PC分别指向下一条将被执行的指令。
线程3正在执行一个本地方法,它的PC值是不确定的。
Java虚拟机中的数据类型分为两种:基本类型和引用类型。
基本类型的变量持有原始值,而引用类型的变量持有引用值。
关于基本类型boolean,当编译器把Java源码编译为字节码时,会用int或byte表示boolean。在Java虚拟机中,false是用整数0来表示的,所有非零整数都是true。涉及boolean的操作则会使用int,boolean数组是当做byte数组访问的。
基本类型returnAddress,是只在虚拟机内部使用的基本类型,用来实现Java程序中的finally字句。
其他基本类型都是数值类型,包括整数类型,浮点数类型。
注:
基本类型的数据不能看作对象,存放在栈中。基本类型都对应有包装类,包装类就是对象了,分配在堆中,栈中保存的是堆内对象的引用。引入包装类的目的是为了让这些数据具有对象性质,可以调用对象的方法。
引用类型有3种:类类型,接口类型,数组类型,还有一种特殊的引用值null。
类装载器子系统,负责查找并装载类型。
Java虚拟机有两种类装载器:启动类装载器和用户自定义类装载器。前者是Java虚拟机实现的一部分,后者是一个普通的Java对象。
由不同的类装载器装载的类,会放在虚拟机内部的不同命名空间中。
对于每一个被装载的类型,Java虚拟机都会在内存堆中为它创建一个java.lang.Class类的实例来代表该类型,而装载的类型信息则都位于方法区。
每个类装载器都有自己的命名空间,其中维护着由它装载的类型。所以,一个Java程序可以多次装载具有同一个全限定名的类型,类型的全限定名不足以确定它在Java虚拟机中的唯一性。对于每一个被装载的类型,Java虚拟机都会记录装载它的类型装载器。
当虚拟机装载某个类型时,先使用类装载器定位并读取相应的字节码文件,然后提取其中的类型信息,将这些类型信息和类的静态变量存储到方法区中。
由于所有线程都共享方法区,因此方法区中的数据访问必须被设计为线程安全的。假设同时有两个线程来访问一个类,而这个类还没有被装载,那么,只应该有一个线程去装载它,另一个线程等待。
在Java源码中,全限定名由类所属的包的名称加一个“.”再加上类名组成。
一个Java程序独占一个Java虚拟机实例,每个Java虚拟机实例都有自己的堆空间,堆空间由Java程序的各线程共享。