我们知道一台电脑有内存,寄存器,硬盘的存储区域,那么对应JVM虚拟机,也有他自己的存储区域。
1.程序计数器:
这是一块比较小的内存单元,是用来记录程序的运行位置。由于在java虚拟机中的多线程是轮流切换并分配时间的方式来实现的。在任何一个确定的时间,一个内核都会执行一条线程的指令。为了线程切换后能恢复到正确的执行位置,所以每一个线程都需要一个自己的程序计数器,所以程序计数器是线程私有的(即线程之间互不影响,独立存储)。程序计数器是唯一一个没有规定任何的OutOfMemoryError情况的异常。
字节码解释器工作时就是通过改变这个计数器的值来选择下一条字节码指令地址。
如果正在执行的是一个JAVA方法,那么计数器记录的是在执行的虚拟机字节码指令的地址。如果是native方法,那么这个计数器值为空。
2.虚拟机栈
虚拟机栈是我们常说的栈,虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用到结束的过程,对应着一个一个栈帧从入栈到出栈的过程。虚拟机栈是线程私有的。
局部变量表:
用来存储八大基本类型以及对象引用(reference,可能是一个指向对象起始位置的指针,也可能是一个指向对象句柄的指针)。
在八大基本类型中long,double用2个单位(Slot)存储,其他用1个单位存储。局部变量分配是在编译前执行的,在运行时并不会改变局部变量表的大小。
在这个区域存在两种异常情况,如果线程申请的深度大于虚拟机栈所允许的深度,如果不能动态扩展,将会抛出StackOverflowError异常。如果能够动态拓展,但是拓展无法申请到足够的内存,那么抛出OutOfMemoryError异常。
3.本地方法栈
本地方法栈与虚拟机栈发挥的作用是非常相似的,只不过虚拟机栈是为Java执行方法服务的,而本地方法栈是为了虚拟机使用的Native方法服务。在使用规范中对本地方法栈中使用的语言,使用方式与数据结构都没有明确的规范,因此虚拟机可以自由是实现它。
4.JAVA堆
堆是java内存上最大的一块区域,是被所有线程共享的。此区域唯一目的就是存放对象实例,几乎所有的实例对象,数组都要在堆上分配。但是随着JIT编译器的发展,逃逸分析技术,栈上分配,标量替换优化技术导致了一些微妙的变化,所有实例对象在堆上分配不在那么绝对了。
堆是垃圾管理的主要区域,现在来及回收主要采用分代算法,所以堆被分为如下:
由于堆是线程共享的,那么在多线程情况下分配就会出现问题,虚拟机通过为每个线程分配私有缓冲区(TLAB)来保证分配不会冲突。
堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,好比我们的磁盘,可以设置为固定的,也可以动态拓展。但是当堆无法申请到足够的空间,那么抛出OutOfMemoryError异常。
5.方法区
方法区使用来存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,它与堆相同,也是线程共享的。虚拟机对方法区的要求很少,除了和堆一样不需要连续的内存和可以选择固定大小或可拓展外,还可以不选择垃圾回收机制。但是并不是不进行垃圾回收,只是非常少,这块区域的主要回收目的是针对于常量池的回收和类型的卸载。但回收效果也很难让人满意。
方法区中主要分为以下部分:
字符串常量池:主要用来存储字符串,在1.7以后被移除永久代。
类常量池:存储类的版本,字段,方法,接口等描述信息。
运行时常量池:用来存储编译器生成的各种字面量和符号引用以及翻译出来的直接引用,具备动态性,并且虚拟机堆这部分没有做任何细节的要求。
当方法区拓展不能申请到足够的空间时,抛出OutOfMemoryError异常。
6.直接内存
这一部分既不是虚拟机运行时数据区的一部分,也不是JAVA虚拟机定义的内存区域,但这由于在JDK1.4加入NIO类,引入了一种基于通道与缓冲区的I/O方式,它可以使用navicat函数库直接分配堆外内存,可能会出现OutOfMemoryError异常。