1.运行时数据区组成概述
JVM的运行时数据区,不同虚拟机实现可能略微有点不同,但是都会遵从Java虚拟机规范,Java 8
虚拟机规定,Java虚拟机所管理的内存都将会包括以下几个运行时数据区域:
1.程序计数器(Program Counter Register)
程序计数器(Program Counter Register)是一块比较小的内存空间,他可以看做是当前线程所执行的字节码的行号指示器。
2.Java虚拟机栈(Native Method Stack)
描述的是Java方法执行的内存模型,每个方法在执行的同时会创建一个线帧(Stack Frame)用于存储局部变量表,操作数帧,动态链接,方法出口等的信息,每个方法从调用直至执行完成的过程,都对应一个线帧在虚拟机栈中从入栈到出栈的过程。
3.本地方法栈(Native Method Stack)
与虚拟机栈作用一样的,只不过虚拟机方法栈是服务于Java方法的,而本地方法栈是为虚拟机调用Native 方法服务的。
4.Java堆(Java Heap)
是Java虚拟机中内存中最大的一块,是被所有线程共享的,在虚拟机启动时创建,Java堆唯一的目的就是存放对象的实例,几乎所有的对象实例都在这里分配内存
5.方法区(Method Area)
用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译后的代码等数据。方法区是很重要的系统资源,是硬盘和CPU的中间桥梁,承载着操作系统和应用程序的实时运行。
JVM内存布局规定了Java在运行过程中内存申请,分配,管理的策略,保证了JVM的高效稳定运行。不同的JVM对于内存的划分方式和管理机制存在着差异,我们以现在最流行的HotSpot虚拟机为例讲解。
你是否可以看懂这张图?
Java虚拟机定义了序运行期间会使用的运行数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是线程一一对应的。这些与线程对应的区域会随着线程开始而结束而创建销毁。
如图: 红色的为多个线程共享,灰色的为单个线程私有的,即线程间共享:堆,方法区。
线程私有:程序计数器,栈,本地方法栈。
2.程序计数器(Program Counter Register)
JVM中的程序计数寄存器(Program Counter Register)命名源于CPU的寄存器,寄存器存储指令相关的信息。CPU只有把数据装载到寄存才能运行。
这里翻译为程序计数器,更容易理解,JVM中的程序计数器是对物理PC寄存器的一种抽象模拟。
程序计数器用来存储下一条指令的地址,也要将要执行的指令代码。由执行引擎读取下一条指令。
- 它是一块很小的内存空间,几乎可以忽略不计,也是运行速度最快的存储区域。
- 在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程生命周期保持一致。
- 任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法,程序计数器会存储当前线程正在执行的Java方法的JVM指令地址。
- 它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。
- 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
- 它是唯一在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
3.Java虚拟机栈(Java Virtual Machine Stacks)
1.栈的基本概念
栈是运行时基本单位,即栈解决程序的运行问题,即程序如何执行,或者说如何清理数据。
Java虚拟机栈(Java Virtual Machine Stacks),早期也叫Java栈,每个线程在创建都会创建一个虚拟机栈,器内部保存一个个栈帧,对应着一次方法的调用Java虚拟机栈是线程私有的,主管Java程序的运行,他保存方法的局部变量(8种数据类型,对象的引用地址),部分结果,并参与方法的调用和返回。
2.栈的特点
栈是一种快速有效的分配存储的方式,访问速度仅次于程序计数器。
JVM直接对Java栈的操作只有两个:调用方法入栈,执行结束后出栈。
对于栈来说不存在垃圾回收问题。
栈中会出现异常,当线程请求的栈深度达与虚拟机所运行的深度是,会出现StackOverflowError
3.栈的运行原理
- JVM直接对Java栈的操作就两个,就是对栈帧的入栈和出栈,遵循先进后出/后进先出的原则
- 在一条活动的线程中,一个时间点上,只会有一个活动栈,即只有当前在执行的方法的栈帧(栈顶)是有效地,这个栈帧被称为当前栈(Current Method),定义这个方法的类被称为当前类(Current Class)。
- 执行引擎运行的所有字节码指令只针对当前栈帧进行操作。
- 如果在该方法中调用了其他方法,对应的新的栈帧就会被创建出来,放在栈顶端,成为新的当前栈帧,成为新的栈帧。
不同的线程中所包含的栈帧(方法)是不存在相互引用的,即不可能在一个栈中引用另一个线程的栈帧(方法)。
如果当前的方法调用其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着虚拟机会丢弃当前的栈帧,使得前一个栈帧重新成为当前栈帧。
Java方法有两种返回的方式,一种是正常的函数返回,使用return指令,另一种是抛出异常,不管哪种方式,都会导致栈帧被弹回。
4.栈帧的内部结构
每个栈帧中存储着:
(1)局部变量表(Local Variables)
局部变量表是一组变量值存储空间,用于存放方法和参数和方法内部定义的局部变量。对于基本数据类型的变量,则直接存储它的值 ,,对于引用类型的变量,则存的是指向对象的引用。
(2)操作数栈(Openrand Stack)(或表达式栈)
栈最典型的一个应用就是用来对表达式求值。在一个线程执行方法的过程中,实际就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是借助于操作数栈来完成的。
(3)动态链接(Dynamic Linking)(或指向运行时常量池的方法引用)
因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量
(4)方法返回地址(Retuen Address)(或方法正常退出或者异常退出的定义)
当一个方法执行完毕后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。
5.本地方法栈
1.Java虚拟机栈管理Java方法的调用,而本地方法栈用于管理本地方法的调用。
2.本地方法栈也是线程私有的。
3.允许被实现成固定的或者是可动态扩展的内存大小,内存溢出方面也是相同的。(如果线程请求分配栈容量超过本地方法栈允许最大容量抛出StackOverflowError)
4.本地方法是用C语言写的
5.它的具体做法是在Native Method Stack 中登记native 方法 ,Exeution Engine执行时加载本地方法库