概述
JVM(Java Virtual Machine)是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。它有自己的指令集和执行引擎,可以在运行时操控内存区域。目的是为构建在其上运行的应用程序提供一个运行环境。JVM可以解读指令代码并与底层进行交互:包括操作系统平台和执行指令并管理资源的硬件体系结构。
JVM的定位
JVM定位于字节码和底层平台之间。底层平台是指操作系统(OS)和硬件。操作系统和硬件体系结构在不通的机器上可能不同,但是同一段Java程序可以不用做任何代码修改就能在不同机器上运行。这就归功于JVM,这也是在虚拟环境中执行的程序语言的独特之处。例如,其他程序语言编译器编译的目标代码如C++和Java相比的不同之处在于,C++程序需要被特定平台的编译器重新编译,从而使它能够在不同的体系结构上面运行。而Java代码并不需要做任何改变,因为由Java编译器编译的字节码是在外围的JVM上运行的。JVM负责重新解译由Java编译器生成的字节码,并和底层平台协调工作。
想要运行Java程序,我们需要JVM,因为它提供了字节码的运行环境。Oracle提供了两种产品:JDK(Java开发工具)和JRE(Java运行环境)。JRE是我们安装运行Java程序最基本软件,它和Java类库以及一些运行Java程序所需要的组件一起构成了JVM的一个实现。如果我们想运行一个类文件或一段字节码,仅需要JRE就够了。而JDK是JRE的超集。它包含了JRE提供的所有东西,包含创建类文件的工具如Java编译器、调试器和其他许多开发Java程序需要的工具。所以我们要创建类文件(编译Java源码)时,我们就需要JDK。下面是一张Java API文档的截图(https://docs.oracle.com/javase/8/docs/),注意组成JDK,JRE和Java SE API核心类库的组件;通过这张截图你可以了解JDK和JRE里面都有哪些内容。
JVM的功能
每一个在JRE上运行的Java程序都会创建一个JVM实例,编译后的Java类文件和其他被依赖的类文件会被加载到运行环境中。这一步由类加载器协调完成。
由于操作系统无法直接执行字节码文件,这时候就需要执行引擎将字节码文件解译成底层系统指令,然后交由CPU进行执行,这个过程需要调用其他语言的本地接口库来实现整个程序的功能。
主要功能可以归纳为:
-
加载:通过类加载器加载类文件的过程。
-
链接:链接类文件,提交给JVM在运行时执行。
-
初始化:分配内存和调用类初始化方法设置变量值。
JVM各部分组成及其作用
- Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。
- Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。
- Execution engine(执行引擎):执行classes中的指令。
- Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。
JVM内存模型
其实我们常说的JVM内存模型(运行时数据区域),主要分以下五大部分
- 程序计数器(Program Counter Register):每条线程都有自己的独立程序计数器,记录当前线程所执行的字节码的行号指示器(方便CPU切换回来之后继续执行),字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;
- 栈/Java 虚拟机栈(Java Virtual Machine Stacks):每个线程创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应一个的方法调用,虚拟机栈的栈元素是栈帧,当有一个方法被调用时就代表这个方法的栈帧入栈;当这个方法返回时就代表栈帧出栈。生命周期和线程一致,也就是线程结束了,该虚拟机栈也销毁了。栈帧中存储局部变量表、操作数栈、动态链接、方法出口等信息;
- 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
- Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
- 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。