JVM = java Virtual Machine,java虚拟机。
JVM 包含两个子系统 和 两个组件。
| 组成 | 说明 |
|---|---|
| 类加载系统 ClassLoader 子系统 | 根据给定类的全限定类名(java.lang.object)将字节码文件加载到运行时数据区的方法区 将java代码转换成字节码 |
| 运行时数据区 Runtime Data Area 组件 | 也就是JVM内存,包含线程共享区(方法区、堆),线程独占区(本地方法栈、程序计数器、虚拟机栈) 把字节码加载到内存中,即运行时数据区的堆空间 |
| 执行引擎 Execution Engine 子系统 | 执行classes中的指令 将字节码翻译成底层系统指令,交由CPU去执行 |
| 本地方法接口 Native Interface 组件 | 连接本地方法库,是与其他编程语言交互的接口 在此过程中需要调用其他语言的本地库接口来实现整个程序的功能 |
扩展
java程序运行机制:
1 idea编写java程序,文件后缀名是.java 2 javac编译器,将.java文件编译为字节码文件(.class) 3 类加载器,将.class文件加载到运行时数据区中的堆空间 4 命令解析器执行引擎,将字节码文件翻译成底层系统指令,交由CPU执行 5 这个过程中,需要调用其他语言的本地库接口2 谈谈对运行时数据区的理解?
JVM组成图示:
私有
- 程序计数器 Program Counter Register
| 条目 | 说明 |
|---|---|
| 概念 | 较小的内存空间,可以看成是当前线程执行字节码文件的行号指示器 |
| 作用 | 字节码解释器就是需要通过改变程序计数器的值来获取下一条要执行的字节码指令 |
| 是私有的原因 | java虚拟机的多线程是通过线程轮流切换并分配CPU执行时间来实现的。 任何一个确定的时刻,一个处理器只会执行一个线程中的命令。 为了线程切换后能恢复到正确的执行位置,所以每个线程都需要一个独立的程序计数器,各个线程之间的计数器互不影响。 |
- java虚拟机栈 Java Virtual Machine Stacks
| 条目 | 说明 |
|---|---|
| 概念 | 描述Java方法执行的内存模型 |
| 作用 | 每个方法在执行的同时都会创建一个栈帧(stack Frame),用于存储局部变量表、操作数栈、动态链表、方法出口等信息 |
| 解释 | 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程 |
| 性质 | 它的线程是私有的,生命周期与线程相同 |
| 异常 | 线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常; 扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常 |
局部变量表
存放了编译期可知的各种基本数据类型(boolean、byte、short、char、int、long、float、double),对象引用和returnAddress类型(指向了一条字节码指令的地址)。
空间单位是槽(Slot),64位的long和double类型会占用两个Slot。
局部变量表所需要内存空间在编译期完成分配,当进入一个方法时,该方法需要在帧中分配的局部变量表内存区域是确定的,在方法运行期间不会改变局部变量表的大小。
- 本地方法栈 Native Method Stack
| 条目 | 说明 |
|---|---|
| 概念 | 为虚拟机使用到的Native方法服务 (与java虚拟机栈类似) |
| 抛出异常 | StackOverflowError 和 OutOfMemoryError 异常 |
| 备注 | java虚拟机规范没有对本地方法栈中方法使用的语言、方式和数据结构做出强制规定,所以具体的虚拟机可以自由地实现它。 例如:Sun HotSpot虚拟机直接把Java虚拟机和本地方法栈合二为一。 |
共享
- Java堆
| 条目 | 说明 |
|---|---|
| 概念 | 被所有线程锁共享的一块内存区域,在虚拟机启动时创建 |
| 存在目的 | 存放对象实例,几乎所有的对象实例都在这里分配内存 |
| 又被称为GC堆 | java堆是垃圾收集器管理的主要区域。 从内存回收的角度看,由于现在收集器基本都采用分代收集算法,所以java堆又被细分为:新生代 和 旧生代。 从内存分配的角度,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allcation Buffer,TLAB),为了更好地回收内存和分配内存。 |
| 抛出异常 | 当在堆中没有完成实例分配,且堆无法扩展时,将会抛出OutOfMemoryError异常 |
| 内存区域规定 | java虚拟机规定,java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。 实现时,可以是固定大小的,也可以是可扩展的 |
- 方法区(Method Area)
| 条目 | 说明 |
|---|---|
| 概念 | 各个线程共享的内存区域 |
| 作用 | 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 |
| 理解 | 虽然java虚拟机规范把方法区描述为堆的一个逻辑部分,但它有别名非堆,目的为了与java堆区分开来 |
| 特点 | 不需要连续的内存,可选固定大小,可扩展,可选择不实现垃圾收集(垃圾回收主要针对常量池、类型的卸载) |
| 异常 | 方法区无法满足内存分配需求时,抛出OutOfMemoryError异常 |
运行时常量池 Runtime Constant Pool
方法区的一部分。class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一些信息在常量池中,用于存放编译器生成的各种自变量和符号引用。该内容将在类加载后进入方法区的运行时常量池中存放。
| 区别 | 堆 | 栈 |
|---|---|---|
| 存储内容不同 | 存储Java中的对象实例 成员变量、局部变量、类变量,它们指向的对象都存储在堆内存中 | 存储基本数据类型和方法引用 |
| 共享 or 私有 | 共享,其中对象对所有线程可见 且 可访问 | 私有,每个线程都有一个栈内存,存储的变量只能在所属线程中可见 栈内存可以理解为线程的私有内存 |
| 异常错误 | 没有空间,抛出OutOfMemoryError | 没有空间,抛出StackOverflowError |
| 空间大小 | 较大 | 较小 |
| 作用 | 存储单位 数据存储问题,数据该怎么放、放哪里 | 运行时单位 决定程序如何执行、如何处理数据 |
扩展
每一个线程都会有独立的线程栈,因为每个线程的执行逻辑不同。
| 栈 | 基本数据类型 和 对象的引用 |
|---|---|
| 堆 | java中的对象实例 |
扩展
- 为什么将对象实例存储在栈中?
因为对象实例的大小是不可估量 且 可能会动态改变的,由于栈内存较小,存储在栈中,可能会出现StackOverflowError。
而堆内存较大,出现OutOfMemoryError的可能性较小。 - 为什么基本数据类型 和 对象引用要放在 栈中?
因为基本数据类型占用的空间一般为1-8个字节,需要空间较少;且基本数据类型不会出现动态增长的情况,长度固定,所以栈内存就够用了。
| 原因 | 具体介绍 |
|---|---|
| 软件设计角度 | 分而治之的思想,栈代表处理逻辑、堆代表数据,使得处理逻辑更加清晰 |
| 为了实现共享 | 堆中的数据可以被多个栈共享 共享提供了有效的数据交换方式(共享内存);堆中的共享常量和缓存可以被所有线程访问,节省了空间 |
| 动态增长 | 栈只能向上增长,限制了栈存储内容的能力 堆中的对象可以根据需要动态增长 两者拆分,使动态增长称为可能性,栈中只需要记录一个地址即可 |
扩展
保存系统运行的上下文,需要进行地址段的划分。
栈中一般存放的是基本数据类型和java对象的引用,不存储java对象的实例。
因为栈内存空间较小,而java对象的实例大小不固定且可能会动态改变,存储在栈中可能会出现StackOverflowError。
值传递。
41没有


