运行时数据区域
java虚拟机在执行java程序过程中会把他所管理的内存划分为若干个不同的数据区域。
java虚拟机运行时数据区
方法区(线程共享区),虚拟机栈,本地方法栈,堆(线程共享区),程序计数器
1.程序计数器
程序计数器是一块较小的内存空间,他可以看作当前线程所执行的字节码指示器。每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立储存,我们称这类内存区域为“线程私有”的内存。
若线程执行的时java方法,计数器记录的是正在执行的虚拟机字节码的指令地址。若执行的是native方法,则这个计数器为空。此内存区域是唯一一个在java虚拟机规范中没有规定任何内存溢出情况的区域。
2.java虚拟机栈
java虚拟机栈也是线程私有的,生命周期与线程相同。
虚拟机栈描述的是java方法执行的内存模型,方法执行的同时会创建一个栈帧(用于储存局部变量表,操作数栈,动态链接,方法出口等信息),每一个方法从调用直到执行完成的过程就对应着栈帧在虚拟机栈中入栈何出栈的过程。
局部变量表存放着编译期可知的基本数据类型,对象引用类型,何returnAddress类型(指向了一条字节码指令的地址)。
long和double类型数据会占用2个局部变量空间(slot),其余的占用一个。局部变量表所需的内存在编译期间完成分配。
java虚拟机此内存区域规定了两种异常状况:线程请求栈深度大于虚拟机允许的深度则抛出 stackoverflow异常。若虚拟机栈可以动态扩容,但是申请的内存无法足够申请的时候,会抛出outofmemoryerror异常。
3.本地方法栈
本地方法栈执行native方法,其与java虚拟机栈的作用相似。异常抛出的规范也和虚拟机栈一样。
4.java堆
大多数情况下,java堆是java虚拟机中最大的一块内存,在虚拟机启动时创建,唯一的作用就是存放java的实例对象,几乎所有的实例都在java堆中分配,不过随着jit编译器的发展和逃逸技术的成熟,栈上分配,标量替换导致这方面会发生一系列变化。
java堆是垃圾收集器的主要管理区域,因此很多时候也被称为GC堆,从内存回收角度看,java堆被分为新生代和老年代。从内存分配的角度看,线程共享的java堆中可能划分出多个线程私有的分配缓冲区。
java堆可以处于物理内存中不连续的内存空间中,只要逻辑上连续即可,就像我们的磁盘空间一样。既可以固定大小,也可以扩展。
java虚拟机规定此区域抛出异常为outofmemoryerror。
5.方法区
方法区和java堆一样,,是各个线程共享的内存区域,他用于储存已被虚拟机加载的类信息,常量,静态变量,即使编译器编译后的代码数据。可以不实现垃圾收集,
java虚拟机规范此区域的异常有outofmemoryerror异常。
5.1.运行时常量池
运行时常量池是方法区的一部分,class文件除了有类的版本,字段,方法,接口等描述信息还有一项是常量池。用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入运行时常量池中存在放。一般来说除了保存class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。
运行时常量池相对class文件常量池还具备动态性,常量并不一定要编译期才能产生,并非class文件的常量池才能放入,运行期间也可能将新的常量放入。
java虚拟机规范的异常和java方法区一样。
6.直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。本机的直接内存的分配不会收到java堆的影响。但是会受到本机内存大小和处理器寻址空间的限制。
例如NIO类,使用native函数直接分配堆外内存。
如果忽略了直接内存,虚拟机配置内存的时候,使各个内存总和大于内存限制。出现outofmemoryerror异常。
关注笔者公众号,推送各类原创/优质技术文章 ⬇️