JAVA系列: JVM虚拟机结构
JVM虚拟机体系结构
方法区和堆由所有线程共享,其他区域都是线程私有的
1程序计数器
为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存,这在某种程度上有点类似于“ThreadLocal”,是线程安全的。
如果正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指定的地址;
如果正在执行的是Native方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
2虚拟机栈
虚拟机栈也是线程私有的,每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈,虚拟机栈表示Java方法执行的内存模型,每调用一个方法,就会生成一个栈帧(Stack Frame)用于存储方法的本地变量表、操作栈、方法出口等信息,当这个方法执行完后,就会弹出相应的栈帧。
每一个方法被调用直到执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
如果请求的栈的深度过大,虚拟机可能会抛出StackOverflowError异常,如果虚拟机的实现中允许虚拟机栈动态扩展,当内存不足以扩展栈的时候,会抛出OutOfMemoryError异常。
栈帧
栈帧分为三部分:局部变量区(Local Variables)、操作数栈(Operand Stack)和帧数据区(Frame Data)。
局部变量区(Loca Variables)
局部变量区存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型。
1)基本数据类型:(int、char、byte、long、boolean、short、float、double)
2)对象引用:(reference类型,它不等同于对象本身,根据不同虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或其他与此对象相关的位置)
3)returnAddress类型:指向了一条字节码指令的地址。
操作数栈(Operand Stack)
操作数栈也被组织为一个字数组,但不同于局部变量区,它不是通过数组下标访问的,而是通过栈的Push和Pop操作,前一个操作Push进的数据可以被下一个操作Pop出来使用。
帧数据区(Frame Data)
这部分的作用主要有三部分:
1. 常量池中数据的解析;(动态链接,是指如果方法里面有接口的实现,这个时候就会动态查找具体的实现类)
2. 方法执行完后处理方法返回,恢复调用方现场 ;
3. 方法执行过程中抛出异常时的异常处理,存储有一个异常表,当出现异常时虚拟机查找相应的异常表看是否有对应的Catch语句,如果没有就抛出异常终止这个方法调用。
3本地方法栈
与虚拟机栈类似,只是是执行本地Native方法时使用。
有的虚拟机直接把本地方法栈和虚拟机栈合二为一。本地方法也会抛出StackOverflowError和OutOfMemoryError异常
4JVM堆
Java 堆也是属于线程共享的内存区域,它在虚拟机启动时创建,是Java 虚拟机所管理的内存中最大的一块。用于存放对象与数组实例的地方,垃圾回收的主要区域就是这里(还可能有方法区)。
此区域唯一的目的就是存放对象实例,几乎所有的对象实例和数组都在这里分配内存。
如果垃圾收集算法采用按代收集(目前大都是这样),这部分还可以细分为新生代和老年代。
新生代又可能分为Eden区,From Survivor区和To Survivor区,主要是为了垃圾回收。所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)。
Java堆只要求逻辑上是连续的,在物理空间上可以不连续。
Java堆是按照可扩展来实现的(通过-Xmx、-Xms控制)
如果没有内存来完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常
5方法区
方法区属于线程共享的内存区域,又称Non-Heap(非堆),主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。值得注意的是在方法区中存在一个叫运行时常量池(Runtime Constant Pool)的区域,它主要用于存放编译器生成的各种字变量和符号引用,这些内容将在类加载后存放到运行时常量池中,以便后续使用。
常说的“永久代”就在方法区。