01.内存结构
JVM 内存结构
JVM 是典型的基于栈的架构,不同于 Android 操作系统设计的 Dalvik 虚拟机采用的基于寄存器的架构,其在单运算的操作次数上会多于基于寄存器的架构。
Java 虚拟机会将内存分为几个不同的管理区,这些区域各自有各自的用途,根据不同的特点,承担不同的任务以及在垃圾回收时运用不同的算法。总体分为下面几个部分:
-
堆(Heap):所有的对象实例和数组,根据虚拟机规范的规定,Java 堆可以是固定的大小也可以是按照需求动态扩展的,而且不需要保证是连续的。
-
元空间(Metaspace):类的结构信息,如类的字段、方法、接口、构造函数,还有运行时常量池等。在 Java 8 之前,PermGen 是存放在堆中,在 Java 8 之后,PermGen 被 Metaspace 替代,并且 Metaspace 直接存放在原生内存,而不再是堆中。
-
程序计数器(Program Counter Register):如果线程执行的是一个 Java 方法,那么寄存器里面记录的就是正在执行的虚拟机字节码指令的地址,如果线程执行的是一个 native 方法,那么寄存器记录的值为 undefined。程序计数寄存器也是虚拟机规范里面唯一一个没有规定任何 OutOfMemoryError 情况的区域。
-
虚拟机栈(JVM Stacks):局部变量表、操作数栈、方法出口等信息,局部变量表存放了编译时期可知的各种基本数据类型、对象引用和指向了一条字节码指令的地址。
-
本地方法栈(Native Method Stacks):局部变量表、操作数栈、方法出口等信息。
除此之外,还有 JVM 内存管理之外的一个内存区:直接内存。在 JDK1.4 中新加入类 NIO 类,引入了一种基于通道与缓冲区的 IO 方式,它可以使用 Native 函数库直接分配堆外内存,即我们所说的直接内存,这样在某些场景中会提高程序的性能。
Java 内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。每当创建一个新的线程时,JVM 会为该线程创建一个虚拟机栈,同时会为这个线程分配一个 PC 寄存器,并且这个 PC 寄存器会指向这个线程的第一行可执行代码。每当调用一个新方法时会在这个栈上创建一个新的栈帧数据结构,这个栈帧会保留这个方法的一些元信息,如在这个方法中定义的局部变量、一些用来支持常量池的解析、正常方法返回以及异常处理机制等等。
如果线程清求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;如果虚拟机栈可以动态扩展 (当前大部分的 Java 虚拟机都可动态扩展,只不过 Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会拋出 OutOfMemoryError 异常。