Java虚拟机划分的运行时数据区有5部分:本地方法区、虚拟机栈、本地方法栈、堆、程序计数器,程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭。
一、程序计数器
当前线程所执行的字节码的行号指示器,字节解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。每个线程之间都有一个独立的程序计数器,它们之间互不影响、独立存储。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutofMemoryError情况的区域。
二、Java虚拟机栈
线程私有,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型。每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法从调用到执行的过程就是栈帧在虚拟机栈中入栈到出栈的过程。这部分可能出现StackOverflowError和OutOfMemory。
三、本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。可能出现StackOverflowError和OutOfMemory。
四、Java堆(新生代和老年代)
是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块区域,在虚拟机启动时创建。此内存区域的唯一目的是存放对象实例,几乎所有对象都在此分配内存。是垃圾收集器管理的主要区域,故也被称GC堆。现在垃圾收集器基本都采用分代收集算法,所以Java中的堆还可以细分成新生代(朝生夕死)和老年代(千年王八),再细致一点有Eden空间、From Survivor空间、To Survivor空间。从内存分配来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。进一步划分的目的是更好的回收内存,或者更快的分配内存。
五、方法区(永久代)
各个线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
java虚拟机规范把方法描述为堆的一个逻辑部分,但他却又一个别名叫Non-Heap(非堆: java7之前,方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时可以设置一个固定值,不可变; java7中,static变量从永久代移到堆中; java8中,取消永久代,方法存放于元空间(Metaspace),元空间仍然与堆不相连,但与堆共享物理内存,逻辑上可认为在堆)永久代容易造成内存溢出。永久代不代表就“永久”存在了,这个区域的内存回收目标只要是针对常量池的回收和对类型的卸载。
运行时常量池
是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池,用于存放编译期生成的各种字面量和符号引用。 当常量池无法再申请时就会抛出OutOfMemoryError异常。