运行时数据区域
每个区域都有各自的用途,以及创建和销毁的时间。
- 程序计数器:一块较小的内存,可以看作当前线程所执行的字节码的行号指示器,通过修改这个技术器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、线程恢复等基础功能都依赖计数器来完成。JVM的多线程是通过线程轮流处理切换、分配处理器执行时间来实现的,在任何时刻一个处理器中只执行一个线程中的指令。为了线程切换后能再次回到正确的执行位置,每个线程需要各自独立的程序计数器,互不影响。因此线程私有,生命周期随线程。
- 虚拟机栈:描述java方法执行的内存模型,每个方法被执行的时,jvm都会同步创建一个栈帧用于存储局部变量表(存放了编译期可知的各种基础数据类型(long和double类型数据占用两个变量槽)8种、对象引用(reference类型)、returnAddress类型(指向一条字节码指令的地址),局部变量表所需内存空间在编译期间完成分配,当进入一个方法时,栈帧中需要的局部变量空间是完全确定的且在运行期间不会改变局部变量表的大小(变量槽的数量))、操作数栈、动态连接、方法出口等信息。每个方法被调用到执行完毕,对应着一个栈帧的从入栈到出栈的过程。线程私有,生命周期随线程。线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常,如果栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。
- 本地方法栈:为使用到的native方法服务。
- 堆:jvm所管理的内存中最大的一块,被所有线程共享的一块区域,虚拟机启动时创建,唯一目的就是存放对象实例。在分配内存的角度,所有线程共享的java堆可划分出多个线程私有的分配缓冲区(Thead Local Alloction Buffer,TLAB)。通过参数-Xmx和-Xms进行设定内存大小,若没有足够的内存分配对象实例,且堆无法扩展,则会抛出OutOfMemoryError异常。
- 方法区:存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。(-XX:MaxPermSize 进行设置内存大小),JDK8已经完全废弃了永久代的概念,在本地内存(Native Memory)中实现的元空间(Meta-space)来代替,也就是说现在存储的地方全部转移到了元空间。针对这个区域的回收主要是针对常量池的回收和类型的卸载。
- 运行时常量池:它是方法区的一部分,Class文件除了有类的版本、字段、方法、接口等描述信息外,还有就是常量池表,用于存储编译期间生成的各种字面量与符号引用,并在类加载后存放到方法区的运行时常量池中。具有动态性,并不要求常量一定只有在编译期才能产生,并非预置入Class文件中常量池的内容才能进入方法区运行常量池,运行期间也能将新的常量放入池中,String.intern() 方法。受方法区内存限制,常量池无法申请到内存时也会抛出OutOfMemoryError异常。
- 直接内存:不是虚拟机运行时数据区域的一部分。也不是jvm规范定义的内存,也会被频繁使用,也可能引起OOM。 比如NIO中引入了一种基于通道(channel)和缓冲区(buffer)的I/O方式,通过Native函数库直接分配堆内存,通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。不受堆内存限制。但会受本机总内存大小和处理器寻址空间的限制。