注意:内容系学习自周志明老师的《深入理解Java虚拟机第二版》,本文只作为学习笔记。
第2章 Java内存区域与内存溢出异常
2.2 Java运行时数据区域
- 什么是运行时数据区域?
Java运行时,将内存划分为几个区域,每个区域有各自的用途、创建和销毁时间。
Java虚拟机管理的内存,可划分为以下几个内存区域。后面的小节将一一讲解。
2.2.1 程序计数器
- 什么是程序计数器,程序计数器有什么作用?
程序计数器是一块内存,在这个内存中有多个计数器。
这些计数器,是线程所执行的字节码的行号指示器。字节码解释器工作时就是通过这个计数器的值来确定下一条需要执行的字节码指令。
.
Java虚拟机的多线程是通过线程轮流切换和分配处理器执行时间来实现的。因此,为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器。一个程序计数器是一个线程私有的。
.
如果线程执行的是Java方法,那么程序计数器的值就是正在执行的字节码指令的地址。
如果正在执行的是 Native(本地)方法,那么程序计数器的值为空。
2.2.2 Java虚拟机栈
虚拟机栈是一块内存,这块内存中保存一些栈帧。
什么是栈帧呢?栈帧保存着Java方法执行的内存模型,内存模型指局部变量表、操作数、方法出口等。其中局部变量表的所需的内存空间在编译期间即完成分配,在方法执行期间不会再改变。
一个方法对应一个栈帧(线程私有),每个方法在执行的同时都会建立一个栈帧,方法从调用直至完成的过程就对应着一个栈帧在虚拟机栈中入栈和出栈的过程。
Java虚拟机规范中,对这个区域规定了两种异常状况:
- StackOverflowError 栈溢出:栈深度无法满足需求。
线程请求的栈深度大于Java虚拟机允许的深度。 - OutOfMemoryError 内存溢出:剩余内存无法满足需求。
虚拟机栈可以动态扩展,扩展时申请不到足够的内存。
2.2.3 本地方法栈
与虚拟机栈相似,虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈也为虚拟机执行的Native方法服务。(线程私有)
备注:Native方法可以调用存储在其他dll文件中的C方法。可参考详解native方法的使用
与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError、OutOfMemoryError异常。
2.2.4 Java堆
Java堆是所有线程共享的一块内存区域。
该区域的唯一目的是存储对象实例、数组。
垃圾收集器管理的主要区域即Java堆,所以堆也叫“GC堆”(Garbage Collected Heap,GC堆到了国内被翻译成Java堆,幸好没有被翻译成垃圾堆)
从内存回收的角度看,由于现在收集器采用分代收集算法,所以Java堆中还可以细分为:新生代、老年代
从内存分配 的角度看,线程共享的Java堆可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer TLAB)。不管怎么分配,都与存放内容无关,存储的都是对象实例。
Java虚拟机规范中,对这个区域规定了一种异常状况:
- OutOfMemoryError 剩余内存无法满足需求。
分配实例时没有足够内存,并且堆也无法再扩展。
2.2.5 方法区
方法区和Java堆一样,是所有线程共享的。
方法区中主要存储:常量(例如字符串)、静态变量、虚拟机加载的类信息等。
Java虚拟机规范将方法区描述为堆的一个逻辑部分,但是它有个别名Non-Heap(非堆),目的是与Java堆区分开来。
该区域的内存回收目标主要是针对常量池和类型的卸载
Java虚拟机规范中,对这个区域规定了一种异常状况:
- OutOfMemoryError 剩余内存无法满足需求。
方法区无法满足内存分配需求。
运行时常量池是方法区的一部分。
class文件中有一项信息是常量池,用于存放编译期生成的字面量和符号引用。这部分内容在类加载后,存放到方法区的运行时常量池区域中。
也就是说class文件中有一个常量池,方法区中有一个运行时常量池。运行时常量池相对于class文件中的常量池有一个重要特征,即动态性。即预置入class文件中的常量池可以进入运行时常量池,运行期间也可以将新的常量放入常量池中。这种特性被开发人员利用的最多的是 String类的 intern() 方法。
intern()的本质是改变字符串引用的方向,让等价字符串对象的引用,都指向同一个字符串对象,使得多余的字符串对象可以被回收。可参考:java字符串String.intern()方法的实际作用与应用场景
Java虚拟机规范中,对这个区域规定了一种异常状况:
- OutOfMemoryError 剩余内存无法满足需求。
常量池无法再申请到内存。
2.2.6 直接内存
直接内存即计算机的本机内存,不是虚拟机运行时数据区域,也不是虚拟机规范中定义的内存区域。
JDK1.4中引入了一种基于通道和缓冲区的IO方式,可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的对象DirectByteBuffer作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,避免了在Java堆和 Native堆中来回复制数据。
Java虚拟机规范中,对这个区域规定了一种异常状况:
- OutOfMemoryError 剩余内存无法满足需求。
服务器管理员设置虚拟机参数时,各个内存区域的总和超出了本机内存限制。