Java虚拟机内存模型主要分为以下5区域:程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。其中前3个为线程私有的,即每个线程都有一个独有的该内存区域,后2个为所有线程共有。
接下来对这5大内存区域进行逐一分析~
一. 程序计数器
这个应该是内存模型里相对最容易理解的了。。程序计数器指示的是当前线程所执行的字节码文件的行号指示器。简单地说,就是告诉线程下一步应该做什么。因此不难理解,程序计数器应该是线程私有的。而且程序计数器所占用的内存非常非常小,几乎可以忽略不计了。
值得一提的是,当线程执行的是java方法时,程序计数器中的数值为当前执行的字节码地址;若是执行native方法,程序计数器中的内容为空。
另外,此内存区域是java虚拟机规范中唯一没有规定OutOfMemory的区域。
二. 虚拟机栈
这部分内存区域主要是为java方法服务的。虚拟机栈中的元素是一个个栈帧,当调用一个方法的时候,栈帧进栈,方法结束时,栈帧出栈。
那栈帧又是一个怎样的数据结构呢?其实也很简单,只要想一下一般的方法中包括哪些东西就可以了。
栈帧主要包括局部变量表、操作数栈、动态链接及方法出口信息等。其中局部变量表存放的是方法的参数、局部变量、对象引用(不同于对象本身,可能是一个指向对象初始位置的指针、代表对象的句柄或其他与此对象相关的位置)和returnAddress(指向下一条字节码指令的地址)等。
值得注意的是,局部变量表中有一个局部变量空间(slot)的概念,一个局部变量空间占32位。因此long和double类型的数据占用2个slot,其他数据类型占用1个slot。jvm对一个栈帧分配需要分配多少局部变量空间在编译期就已经确定了,在运行期间不会改变。
三. 本地方法栈
本地方法栈和虚拟机栈的功能相似,区别只是本地方法栈是为native方法服务的。
四. 方法区
个人认为这是最坑的一个部分=。=
官方名称:方法区。有些文章也喜欢叫“永久代”。(之前一直以为永久代在堆里....)实际上是因为HotSpot虚拟机的设计团队将堆中的GC机制扩展到了方法区中,而在其他虚拟机中是不存在永久代这个概念的。目前已经渐渐不使用这种说法了。
言归正传,方法区中存放的是已被虚拟机加载的类信息、常量、静态变量和即时编译器编译后的代码等数据。
常量池
常量池一般分为静态常量池和运行时常量池。
经过编译的class文件中除了有类的版本、字段、方法、接口等描述信息外,还有包括常量池的信息,这就是静态常量池,里面一般存放编译期生成的字面量(各种基本数据类型和string)和符号引用。
这部分内容当类加载将其放入内存中后,就变成了运行时常量池。运行时常量池与静态常量池最主要的区别就是它是动态的,运行期间也有可能会产生新的常量放入运行时常量池中。比较典型的就是String类的intern()方法,这个方法很重要!!
解释一下intern()方法:当string类型的对象调用intern()方法时检测常量池中是否存在与string值相同的字符串,如果不存在则将string对象放入常量池中(这是1.7的特性,1.6则是仅仅在常量池中新生成一个值相等的字符串),如果存在返回常量池中字符串的引用。
了解了常量池之后,说一下对于方法区的内存回收,也叫作永久代GC。针对这一区域的GC主要是对常量池的回收和对类型的卸载。
常量池的变化:在JDK1.6,运行时常量池位于方法区。到了JDK1.7,由于方法区内存有限,可能会出现内存溢出,因此在堆中开辟一个区域存放常量池。JDK1.8中,方法区被直接放在了本地内存区域,此区域被称为元空间(metaSpace)。可以看出,元空间与永久代最大的区别就是元空间使用本地的内存,而永久代处于虚拟机中。
五. 堆
堆是java虚拟机管理的内存中最大的一块,它里面存放的都是对象实例或数组。堆在物理内存上可以是不连续的,但在逻辑上一定要是连续的。
堆从内存回收的角度可以分为新生代和老年代,更细一点新生代可以分为1个Eden区和2个Survivor区。具体的GC机制可以看我的另外一篇文章。

网友评论