- Java虚拟机的内存区域
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分成若干个不同的数据区域,这些区域都有各自的用途,各自的创建和销毁的时间。有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。 —— 倪升武
Java虚拟机运行时将内存划分成以上几块区域,每个区域都有各自的特点。
1.1 程序计数器
程序计数器是一块较小的内存空间,作用是记录该线程当前执行指令的地址信息,也就是计算机组成原理中说到的PC寄存器。我们知道CPU在执行线程的时候其实是按时间把控制权交给每个线程,所谓的并行从实现上来看还是串行,这样当某一个线程重新获得CPU的控制权的时候,就需要从程序计数器中获取到上一次指令执行的位置信息从而使得线程能从上次中断的地方继续执行下去。为了线程切换之后能恢复到正确的位置,就需要每个线程都拥有独立的程序计数器,且相互之间不会产生影响,因此程序计数器是线程私有的。
而且,程序计数器在线程往下执行的时候只需要改变当前指令的地址信息,并不需要随着线程的执行去申请新的内存空间来存放每一条只指令,因此不会产生内存溢出的情况,因此,程序计数器是JVM唯一一个没有规定 OutOfMemoryError 异常的区域。
当线程执行的是本地方法的时候,程序计数器中保存的值是空(undefined);原因很简单,本地方法是C++/C 写的,由系统调用,根本不会产生字节码文件,因此,程序计数器也就不会做任何记录 。
1.2 Java虚拟机栈
Java虚拟机栈即我们常说的栈内存,线程私有,生命周期与线程同步,Java虚拟机栈是用来描述Java方法执行的内存模型。方法开始执行时,会创建一个栈帧压入栈中,用于存储局部变量表(基本数据类型,对象的引用和returnAddress类型),操作数栈,动态链接,方法出口信息等。局部变量表在编译期确定大小,不会随着方法执行而改变。方法执行结束时,栈帧出栈。
对Java虚拟机来说,可能出现两种异常情况。
- 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError异常。
- 如果虚拟机栈在动态扩展时,无法申请到足够的内存,就会抛出OutOfMemoryError异常。
1.3 本地方法栈
本地方法栈与虚拟机栈的作用很类似,不同的是虚拟机栈是为Java方法的执行提供服务,而本地方法栈是为虚拟机使用到的本地Native方法服务的。本地方法栈也会抛出StackOverFlowError异常和OutOfMemoryError异常。
1.4 Java堆
Java堆是Java虚拟机内存管理中最大的一块,也是JVM垃圾回收机制(garbage collection,gc)的主要作用区域。Java堆的唯一目的是存放实例对象和数组。被所有线程共享,随着JVM的创建而创建。
Java堆在物理上不需要连续的内存,只要逻辑上连续即可。如果堆中没有内存完成实例分配,并且也无法再扩展时,将会抛出OutOfMemoryError异常。
1.5 方法区
方法区也被称为Non-heap(非堆),与Java堆相对,对于HotSpot虚拟机来说,也习惯被称为永久代。其主要作用是用来存放已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,被所有线程共享。相对而言,垃圾收集行为在这个区域比较少出现,但也并非不会来收集,这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载上。
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。
1.6. 运行时常量池
运行时常量池属于方法区。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量表,用于存放编译期生成的各种字面常量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放(JDK1.7开始,常量池已经被移到了堆内存中了)。
也就是说,这部分内容,在编译时只是放入到了常量池信息中,到了加载时,才会放到运行时常量池中去。运行时常量池县归于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区的运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用的比较多的是String类的intern()方法。
常量池属于方法区,同样可能抛出OutOfMemoryError异常。