Java虚拟机运行时的内存数据区域可分为:
其中堆和方法区由线程共享;虚拟机栈、本地方法区、程序计数器线程隔离的,即每个线程都有。
下面简单描述下上述的各个分区的职责和功能
程序计数器是一块较小的内存空间,在java虚拟机的概念模型里面,字节码解释器工作时,通过改变这个计数器来选去下一个要执行的字节码指令,他是程序控制流的指示器;包括:分支、循环、异常处理、线程恢复。
java虚拟机的多线程时通过线程轮流切换、分配处理器执行时间的方式来实现的。因此,为了线程切断后能恢复到正确的执行位置,每天线程都需要一个独立的程序计数器。
Java虚拟机栈时线程私有的,他的生命周期与线程相同。
虚拟机栈描述的是java方法执行的线程内存模型:
每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中从入栈道出栈的过程。
局部变量表存放了编译器可知的各种java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,他并不等同于对象本身,可能是一个指向对象其实地址的引用指针,也可能是指向一个代表对象的句柄会在与此对象相关的位置)。
这些数据类型在局部变量表中的存储空间以局部变量槽来表示,局部变量表在内存空间编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的空间时完全确定的,在方法运行期间不会改变大小。这里的大小指的是变量槽的数据,实际大小和虚拟机真正使用多大的空间有关系;例如一个变量槽占用多少bit。
在这个内存区域有两类异常
HotSpot虚拟机的栈容量时不可扩展的,classic可以。所以在hotspot虚拟机上时不会出现oom异常的,只要线程申请栈空间成功就不会oom(会StackOverflowError),如果申请失败则会时oom;
本地方法栈和虚拟机栈发挥的作用非常相似,区别就是虚拟机栈位虚拟机执行java方法服务,而本地方法方法栈则为虚拟机使用到的本地方法服务。本地方法栈也会抛出StackOverflowError和oom异常。
Java堆事所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里面“几乎”所有的对象实例都在这里分配内存。
Java堆事垃圾收集器管理的内存区域,从内存回收的角度看,现代垃圾收集器大部分都是基于分代收集理论设计的(例如:新生代、老年代;eden空间、from survivor空间、tosurvivor空间),但是也有不分代设计的垃圾收集器。
如果从分配内存的角度看,所有的线程共享的java堆中可以划分出多个线程私有的分配缓冲区(TLAB),以提升对象分配时的效率。
方法区与堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
在hotspot虚拟机上,有的人把方法区称呼为永久代。这个其实是因为当时hotspot虚拟机当时选择把收集器的分代设计扩展至方法区,好省掉为方法区单独编写垃圾收集的工作。到了jdk 7 的hotspot已经把原本放在永久代的字符串常量池、静态变量移出,到了jdk8 已经完全废弃了永久代。
方法区的垃圾收集目标主要是针对常量池的回收和类型的卸载,方法区也会出现oom的异常。
直接内存并不是虚拟机运行时数据区的一部分。
参考:
《深入理解Java虚拟机》周志明