话不多说,先上图吧。
如上图所示,Java内存区域运行时数据区,分为程序计数器、虚拟机栈、本地方法栈、堆、方法区等五个区域。其中,堆和方法区是线程共享的,程序计数器、虚拟机栈和本地方法栈是线程隔离的。下面具体讲一下这五个区域分别做什么的。
程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行代码的行号指示器,主要是为了在程序运行期间,线程切换后能够恢复到正确的执行位置。每条线程都有一个独立的程序计数器,不同线程的程序计数器之间互不影响、独立存储。
如果当前线程执行的是Java方法,则程序计数器记录的是正在执行的字节码的指令地址,如果当前线程执行的是本地方法,则程序计数器的值为空。
虚拟机栈
虚拟机栈描述的是Java方法执行的线程内存模型,每一个Java方法从被调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。栈帧中存储着局部变量表、操作数栈、动态连接、方法出口等信息。
局部变量表存放了Java基本数据类型(byte,short,int,long,float.double,char,boolean)、引用类型(对象的引用)和returnAddress类型的数据
这些类型的数据在局部变量表中的存储空间,以局部变量槽来表示。64位长度的long和double类型的数据,占用两个局部变量槽,其他类型的数据占用一个局部变量槽。局部变量表所需的内存空间在编译期间完成分配,方法执行期间不会改变局部变量表的大小。
本地方法栈
本地方法栈和虚拟机栈类似,虚拟机栈为Java方法服务,本地方法栈为本地方法服务。
堆
不同于程序计数器、虚拟机栈、本地方法栈随线程的创建而分配,随线程的销毁和释放,Java堆在虚拟机启动时创建,是Java虚拟机管理的做大的一块内存。
Java堆唯一的目的就是存放对象实例。
Java堆是垃圾收集器管理的区域。
与Java堆相关的一些诸如“老年代”、“新生代”、“Eden空间”、“Survivor空间”等区域划分,仅仅是一部分垃圾收集器的共同特性和设计风格,并不是《Java虚拟机规范》堆Java堆的进一步划分。
从内存分配的角度看,所有线程共享的Java堆可以分为多个线程私有的分配缓冲区,以提升内存分配的效率。并且,无论从什么角度、如何进一步划分,Java堆的作用都只能是存储对象实例,进一步的划分仅仅是为了更方便的分配和回收内存。
方法区
方法区同Java堆一样,在虚拟机启动时创建,线程共享,
方法区用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
方法区在《Java虚拟机规范》中被描述为Java堆的一个逻辑部分,但是它却有一个别名叫做 "Non-Heap".
很多人习惯将方法区称为 “永久代”(Permanent Generation),因为hotSpot虚拟机的方法区是用永久代实现的。
从JDK1.8开始,hotSpot放弃使用永久代实现方法区,改为使用本地内存,使用永久代实现方法区有两个明显的缺点:
永久代的大小有 -XX:MaxPermSize 的上限,不设置也有默认的上限。
有一些方法会因为永久代的原因导致在不同的虚拟机下有不同的表现,例如:String::intern()