运行时数据区
Java虚拟机运行时,会把它所管理的内存划分为若干个数据区,不同的数据区有着不同的作用,也有着不同的生命周期,有的数据区随着虚拟机的启动而一直存在,而有些数据区依赖于用户线程的启动和结束而创建和销毁。Java虚拟机所管理的运行是数据包括以下几个: 程序计数器,虚拟机栈,本地方法栈,Java堆,方法区。
1. 程序计数器
- 程序计数器是一块较小的内存空间,可以看做是线程所执行字节码的
行号指示器
,它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖于这个计数器来完成。 - 因为线程没有记忆功能,Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能回复i到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程相互独立,互补影响,我们称之为
线程私有
的内存,进而生命周期和线程同步。 - 且程序计数器是虚拟机中唯一没有规定
OutOfMemoryError
情况的区域。
2. Java虚拟机栈
- Java虚拟机也是线程私有的,它的生命周期与线程相同。
- 虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机栈都会同步创建一个栈帧(Stack Frame)用于储存局部变量表、操作数栈、动态连接 、方法出口等信息。每一个方法从调用到执行完毕的过程,就对应着一个栈帧再虚拟机栈中从入栈到出栈的过程。
- 栈帧
- 虚拟机栈中的单位,一个方法对应着一个栈帧,一个栈帧中它又要储存方法中的,局部变量表,操作数栈,动态连接,出口等。
- 栈帧解析
- 局部变量表: 用来储存我们临时八个基本数据类型,对象引用地址,
retunAddress
类型。returnAddress中保存的是return后要执行的字节码的指令地址
。 - 操作数栈:操作数栈就是用来操作的,例如代码中有一个
i = 1+1
,它再一开始的时候就会进行操作,读取我们的代码,进行计算后再放入局部变量表中。 - 动态连接: 假如我方法中,有一个
service.add()
方法,要链接到别的方法中区,这就是动态链接,储存链接的地方。 - 出口: 正常的话就是return,不正常的话就是抛异常。
- 局部变量表: 用来储存我们临时八个基本数据类型,对象引用地址,
3. 本地方法栈
- 本地方法栈和虚拟栈非常相似,其区别只是虚拟机栈为虚拟机执行Java方法服务(也就是字节码服务),而本地方法栈则是为虚拟机用到的本地(native)方法服务
4. Java 堆
- Java堆是虚拟机所管理的最大的一块内存,其被所有线程所共享,在虚拟机启动时创建。
- 此区域的唯一目的就是存放对象实例。Java 世界里几乎所有对象实例都在这里进行分配内存。
- 它是被垃圾回收器所管理的内存区域,因此也被称为
GC堆
。 - 从内存回收角度来看:堆可以分为新生代和老年代
- 从内存分配角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓存区。
- 无论怎么划分,都与存放的内容无关,无论那个区域,储存的都是对象实例,进一步划分都是为了更好的回收内存,或者更快的分配内存。
- Java堆可以处于物理上不连续的内存空间中,当前主流虚拟器都是可扩展的(通过
-Xms
和-Xmx
控制),如果队中没有内存可完成实例分配,且堆也无法再扩展,就会抛出OutOfMemoryError
异常。
5. 方法区
- 和堆一样,方法去也是所有线程所共享的一快内存区域。
- 它用于储存已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
- 如果方法区无法满足新的内存分配需求时,就会抛出
OutOfMemoryError
异常。
6. 直接内存
- 直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机中定义的内存区域,但这部分内存也被频繁的使用,也可能导致
OutOfMemoryError
异常的出现。
虚拟机的运行流程
- 首先通过编译器把Java代码编译成字节码,类加载器(
ClassLoader
)再把字节码文件加载到内存中,将其放在运行时数据区(Runtime data area
)的方法区内,而字节码文件只是JVM
的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine
),将字节码翻译成底层系统指令,再交由CPU去执行,而这个过程中需要调用其它语言的本地库接口(Native Interface)来实现整个程序的功能。