文章目录
- 一、JVM 的作用
-
- 1、Java 程序的运行机制
- 2、Java 如何实现平台无关性?
- 二、JVM 组成
- 三、JVM 内存模型
-
- 1、PC 寄存寄
- 2、方法区
- 3、运行时常量池
- 4、Java 虚拟机栈
- 5、本地方法栈
- 6、堆内存
本文内容基于目前使用最广泛的 HotSpot JVM。
一、JVM 的作用
JVM,即 JAVA 虚拟机(Java virtual machine),是运行 Java 程序必不可少的工具。
JVM 实现了 Java 语言最重要的特征:平台无关性。
1、Java 程序的运行机制
程序员编写的代码都是源代码(.java),不能直接执行,必须通过 javac 编译成字节码文件(.class),最后在 JVM 中运行。

2、Java 如何实现平台无关性?
Java 语言很重要的一个优点就是具有平台无关性:“一次编写,到处运行”,可以在 Windows、Solaris、Linux、MacOS 等各种操作系统平台上运行同样的 java 代码。
Java 实现平台无关性的关键就是 JVM。
因为 Java 程序不是直接运行在操作系统上,而是运行在 JVM 中。而我们在运行 java 程序之前必须先搭建好 java 环境,其实就是在不同的操作系统上安装与系统对应的 JVM ,屏蔽了与具体平台相关的信息,最终给用户的感觉就是平台无关的。
二、JVM 组成
JVM 是通过在实际的计算机上仿真模拟各种计算机功能来实现的 java 虚拟机。
它由一套字节码指令集、一组寄存器、垃圾回收器、堆、栈、存储方法域等组成,如下图所示:
翻译成中文,如下所示:
可以看出,JVM 主要由以下三部分组成:
JVM = 类加载器 classloader + 执行引擎 execution engine + 运行时数据区域 runtime data area
类加载器 classloader 把硬盘上的 class 文件加载到 JVM 中的运行时数据区域中,由执行引擎负责执行,执行过程中产生的数据都放在运行时数据区。
JVM 运行时数据区 (Runtime Data Area) 其实就是指 JVM 在运行期间,对 JVM 内存空间进行管理和分配。
三、JVM 内存模型
JVM 在执行 Java 程序时,会把运行时数据区 (Runtime Data Area) 划分为 6 个区域来存储运行时数据,包括:堆内存(Heap Memory)、虚拟机栈/Java栈(Java Stacks)、本地方法栈(Native Method Stack)、PC 寄存寄(PC Registers)、方法区(Method Area)、运行时常量池。
JVM 内存模型如下图所示:
程序员写的所有程序都被加载到运行时数据区域中,根据数据类型不同分别存放在 6 个内存区域中。
这些区域分别有各自的用途、生命周期,有些依赖虚拟机进程启动而存在,有些依赖用户线程的启动和结束而建立和销毁。
1、PC 寄存寄
PC 寄存寄(PC Registers),也称为程序计数器。
每个线程都会有自己私有的程序计数器,它是一块较小的内存空间,相当于当前线程所执行的字节码程序的行号指示器。
程序计数器区域是唯一一个在 JVM 规范中没有规定任何 OOM (OutOfMemoryError)情况的区域。
2、方法区
方法区,Method Area,是各个 线程共享 的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量等数据。
很多时候,也把方法区称为 “永久代”(Permanent Generation)。
根据 JVM 规范,当方法区无法满足内存分配需求时,可能抛出
OutOfMemoryError
异常。
3、运行时常量池
运行时常量池(Runtime Constant Pool)是方法区中的一部分,其中存放的是类中固定的常量信息、方法和域的引用信息。
该区域受方法区内存的限制,当常量池无法再申请到内存时会抛出
OutOfMemoryError
异常。
4、Java 虚拟机栈
Java 虚拟机栈,即 Java Virtual Machine Stack,通常简称为 VM Stack 或 Java Stack 或 Stack --「栈」,它描述了 Java 方法执行的内存模型。
虚拟机栈具有如下特点:
- 线程私有,生命周期和线程相同;
- 每个方法在执行时都会创建一个栈帧 (Stack Frame),用于存储 局部变量表(方法中定义的变量)、操作数栈、动态链接、方法出口等信息。
- 栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。
- 每个方法从调用直至执行完成的过程,就是对应着一个栈帧在栈中入栈和出栈的过程。
在 JVM 规范中对这块区域规定了两种异常情况:
- 如果线程请求的栈深度大于 JVM 允许的深度,则抛出
异常;StackOverflowError
- 如果虚拟机栈动态扩展时,无法申请到足够的内存,则抛出
异常。OutOfMemoryError
5、本地方法栈
本地方法栈,即
Native Method Stack
,用于执行 Native 方法(简单的说,Native 方法就是用 java 调用非 java 代码的接口,一般是针对一些系统底层的代码)。它与虚拟机栈的作用相似。
Native Method Stack
与
VM Stack
的区别:
-
是为执行 java 方法服务的;VM Stack
-
是为执行本地方法(非 java 代码)服务的;Native Method Stack
- 二者其它的作用相似。
在目前最流行的 Sun HotSpot 虚拟机中,直接把虚拟机栈和本地方法栈合二为一。
与 Java 虚拟机栈一样,本地方法栈也会抛出
StackOverflowError
和
OutOfMemoryError
异常。
6、堆内存
堆内存,即
Heap Memory
,是 JVM 管理的内存中最大的一块。
堆内存具有如下主要特点:
- 在虚拟机启动时被创建;
- 被所有线程共享的一块存储区域;
- 几乎所有的对象实例和数组都要在堆上分配。
堆中存储的对象,无需、也无法显式地被销毁。它们被自动管理内存系统(Automatic Storage Management System,也即是常说的 「Garbage Collector(垃圾回收器)」)所管理。
堆内存的大小可以是固定的,也可以随着程序执行的需求动态扩展,并在不需要过多空间时自动收缩。
根据 JVM 规范,当在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出
OutOfMemoryError
异常。
由于篇幅限制,在下一篇文章中对堆内存进一步探讨。