一、概览
java虚拟机在运行时会把它所管理的内存划分为若干个不同的数据区。每个数据区创建和销毁的时间不同。一些随着虚拟机进程启动而一直存在,一些依赖于用户线程的启动和结束而建立和销毁。数据区域划分如下图所示:
1、方法区(线程共享)
2、堆(线程共享)
3、虚拟机栈(线程私有)
4、本地方法栈(线程私有)
5、程序计数器(线程私有)
二、程序计数器
程序计数器(PC)中保存的数值用来指定下一步要执行的代码的内存地址(字节码的行号指示器)。字节码解释器工作时就是改变PC的值来选取下一条需要执行的字节码指令。
多线程是通过轮流切换、分配处理器执行时间恶方式来实现的,为了在任何时刻可以切换线程都可以恢复当前线程正确的执行位置,需要每个线程拥有自己独立的程序计数器。
特殊:若线程正在执行java方法,PC记录的是正在执行的虚拟机字节码指令的地址;若执行的是本地(Native)方法,这个计数器的值应该为空(Undefined)。
此内存区域是唯一一个没有规定任何OutOfMemoryError情况的区域。
三、虚拟机栈
平时提到最多的内存区域是堆内存和栈内存,在java中栈可以分为虚拟机栈和本地方法栈。
虚拟机栈中是又一个一个栈帧组成的,用来存储局部变量表、操作数栈、动态链接、方法出口等信息。通常描述的栈指的是虚拟机栈中的局部变量表部分。
局部变量表:存放 基本数据类型、对象引用、returnAdress。这些数据在局部变量表中以局部变量槽(Slot)来表示,其中64为长度的long和double类型的数据会占用两个Slot,其余的只占用一个。slot的大小是由虚拟机自行确定的。
操作数栈:用来保存计算过程的中间结果
动态链接:描述一个方法调用的另外的其它方法时,就是通过常量池中指向该方法的符号引用来表示,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
两类异常:StackOverflowError(线程请求的栈深度超过虚拟机允许的深度)和OutOfMemoryError(栈想要扩展时但内存不够)。
下面给出了一个程序中func1对应的虚拟机栈模型(具体细节没有展开):
。。。。。。。。。。。。
四、本地方法栈
本地方法栈(Native M ethod Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机 栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native) 方法服务。
有的Java虚拟机(譬如Hot-Spot虚拟机)直接 就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失 败时分别抛出StackOverflowError和OutOfMemoryError异常。
五、堆
堆是虚拟机管理内存最大的一块,被所有线程所共享的区域,在虚拟机启动时创建。几乎所有的实例对象都是在这里分配内存。
《java虚拟机规范》:“所有的对象实例以及数组都应当在堆上分配”
java堆是垃圾回收管理的区域,也称作“GC堆”。
六、方法区
方法区(仅是JVM的一个规范,具体实现有永久代和元空间)是各个线程共享的内存区,它用于存储已被虚拟机加载的:
1、类型信息
2、常量
3、静态变量
4、即使编译后的代码缓存等数据
JDK8以前很多人把方法区称作“永久代”,本质上是不等价的,仅仅是当时hotSpot设计团队选择把垃圾收集器的分代设计扩展到方法区,或者说使用永久代来实现方法区,让收集器也能管理这部分内存。但后续出现一些问题。
JDK6放弃开始永久代,逐步改为采用本地内存(Native Memory)来实现方法区的计划
JDK7时已经把放在永久代的字符串常量池、静态变量等移出(字符串常量、静态变量移至堆内存)。
JDK8完全废除永久代的概念,改用JRockit、J9一样在本地内存的元空间来代替,并把JDK7永久代中还剩余的内容(主要是类型信息)全部移到元空间中。
jdk7以前的方法区(永久代):
类型信息、常量、静态变量等
jdk8以后的方法区(元空间):把永久代一分为二
类信息、方法信息等放入元空间
静态变量、字符串常量(String)存放在堆中。