Java虚拟机定义了一些程序运行期间会使用到的数据区域,其中一些会随着JVM的启动而创建,随着JVM的退出而销毁;另外一些则与线程的运行一一对立的,这些数据区域会随着线程的开始而创建,随着线程的结束而销毁。下面是一张Java运行时的数据区模型图:
总的来说,Java运行时数据区域可以分为两个部分:线程共享的区域和线程独享的区域。下面一一对之进行总结。
一、线程共享区域:线程共享区域是指各个线程都会使用到的一块空间区域,它们会在这里申请空间、使用空间。根据具体提供功能不同,可以划分为两个部分,分别是Java堆、方法区。下面分别总结。
1、Java堆:Java堆是在虚拟机启动的时候就被创建,它存储了被自动内存管理系统(也即常说的垃圾收集器)所管理的各种对象,这些对象无需也无法被显示地销毁。我们创建的类的实例以及数组对象就存放在这个区域。Java堆所使用的内存不需要保证是连续的。Java堆可能出现的异常有:如果所需的堆超过了自动内存管理系统能提供的最大容量,则Java虚拟机会抛出一个OutOfMemoryError异常。
2、方法区:方法区也是供各个线程共享的运行时存储区,它存储了每一个类的结构下信息,例如运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容等。方法区也是随着虚拟机的启动而被创建,它是Java堆的逻辑组成部分,但是JVM可以选择在这个区域不实行垃圾搜集和压缩。同样,如果方法区的内存不能满足内存分配需求,则JVM将会抛出OutOfMemoryError异常。
3、运行时常量池:它是Class文件中每一个类或接口的常量池表的运行时的表示形式。它包涵了若干不同的常量,从编译期可知的数值字面量到必须在运行期解析后才能获得的方法或字段引用。每个运行时常量池都在Java虚拟机的方法区分配,在类和接口加载到虚拟机后,就会创建对应的运行时常量池。当创建类和接口时,如果构造运行时常量池所需的内存空间超过了方法区所提供的最大值,JVM将会抛出OutOfMemoryError异常。
二、线程独享区域:线程独享区域是每个线程都会单独拥有的一块区域,这块区域是属于线程内部的,它是用于存储描述自身内部信息的区域。同样,根据其提供 主要功能不同,一个线程内部具有的不同数据存储区域可以分为以下几个部分:
1、PC寄存器:PC(program counter)寄存器是用于记录正在执行线程的内存地址的,因为一个线程不可能一直独享CPU,当线程进行CPU切换时,必须要记录下来当前线程的内存地址,以便在重新启动线程时恢复以前的内存地址,在任意时刻,一条线程只会执行一个方法的代码,这个正在被执行的方法被成为该线程的当前方法(current method)。如果这个方法不是native的,那么PC寄存器会保存JVM正在执行的字节码指令的地址;如果该方法是native的,那么PC寄存器的值是undefined。
2、本地方法战:JVM可能会使用传统的栈(通常称为 C stack)来支持native方法(指使用Java以外的其他语言编写的方法)的执行,这个栈就是本地方法栈(native method stack)。JVM规范允许本地方法栈实现成为固定大小或者根据计算来动态扩展和收缩。若采用固定大小的本地方法栈,则每一个线程的本地方法容量可以在创建栈的时候独立选定。
3、Java虚拟机栈:也就是常说的“Java栈”,每一个JVM线程都有自己私有的Java虚拟机栈,该栈是与线程同时创建,用于存储栈帧。它的作用是用于存储一些尚未计算好的结果,此外,它在方法的调用和返回中也具有重要的作用。JVM规范既允许java虚拟机栈被是现成固定大小,也允许根据计算来扩展和收缩。若采用固定大小的Java虚拟机栈,则每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。Java虚拟机栈可能发生的异常有:如果线程请求的栈容量超过java虚拟机栈所允许的最大容量,则java虚拟机会抛出StackOverflowError异常;如果java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存或则创建新的线程时没有足够的内存去创建对应的虚拟机栈,则java虚拟机会抛出OutOfMemoryError异常。
4、栈帧:栈帧(frame)是用来存储数据和部分过程结果的数据结构。同时也是用来处理动态链接(dynamic linking)、方法返回值和异常分派。栈帧随着方法的调用而被创建,随着方法的结束而被销毁(无论是方法正常完成还是异常完成)。栈帧的存储空间是由创建它的线程分配在java虚拟机栈中的。每一个栈帧都有自己的本地变量表(local variable)、操作数栈(operand stack)、指向当前方法所属的类的运行时常量池的引用。在某个线程执行过程中的某个时间点上,只有目前正在执行的那个方法的栈帧是活动的,这个栈帧被成为当前栈帧(current frame),这个栈帧对应的方法称为当前方法(current method)、定义这个方法的类称为当前类(current class)。注意:栈帧是线程本地私有的数据,不能在一个栈帧之中引用另外一个线程的栈帧。
5、局部变量表:也叫本地变量表,它是用于存储局部变量的列表。栈帧中的局部变量表的长度是由编译期决定,并存储与类或接口的二进制表示中。一个局部变量可以保存一个类型为boolean、byte、char、short、int、float的数据类型。两个局部变量可以保存一个类型为long或double的数据。JVM使用局部变量表来完成方法调用时的参数传递。
6、操作数栈:每个栈帧内部均包含一个操作数栈,栈帧在刚被创建的时候,操作数栈是空的。JVM提供一些字节码指令来从局部变量表或者对象的实例中复制常量或变量到操作数栈中,也提供了相关指令从操作数栈中取走数据、操作数据以及把操作结果重新入栈。任意时刻,操作数栈都会有一个确定的栈深度,一个long、double类型的数据会占用两个深度的栈深度,其他数据则会占用一个栈深度。
6、动态链接:每个栈帧都包含这样一个引用,该引用是指向当前方法所在类型的运行时常量池,其作用是对当前代码实现动态链接。一个方法要调用其他方法,或则访问成员变量,需要通过符号引用(symbolic reference)来表示,动态链接的作用就是将这些以符号引用所表示的方法转换为对实际方法的直接引用。由于对其他类中的方法和变量进行了晚期绑定(late binding),所以即便那些类发生变化,也不会影响调用它们的方法。
以上总结自《Java虚拟机规范》,感觉内容还是挺多的,加油!