Java运行时内存结构
由图所示,Java运行时内存区域分为:虚拟机栈、方法区、本地方法栈、程序计数器和堆。
程序计数器
程序计数器可以看作是当前线程所执行的字节码的行号指示器,它只了很小的内存。程序的基础功能(循环、跳转、异常处理、线程恢复等)都需要依赖这个计数器来完成。程序计数器是属于线程私有的。
虚拟机栈
虚拟机栈描述的是Java方法之行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表、操作数栈、动态链表、方法出口等信息。
每一个方法被调用直至执行完成的过程,就是对应一个栈帧在虚拟机从入栈道出栈的过程。
我们平时所关心的栈,可以说是虚拟机栈中的局部变量表,局部变量表存放了编译期可知的基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和returnAddress类型。
JVM规范规定了这个区域的两种异常:
StackOverflowError
如果线程请求的栈深度大于虚拟机所允许的深度时会抛出;
OutOfMemoryError
如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出。
本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,虚拟机栈执行Java方法(字节码)服务,而本地方法栈则是Native方法服务。
本区域会抛出 StackOverflowError 和 OutOfMemoryError异常。
方法区
方法区存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后
的代码。
有些资料说方法区为“永久代(Permanent Generation)”(其实只有HotSpot虚拟机)。
在垃圾回收方面,这个区域很少出现垃圾回收行为,但是不是说没有垃圾回收。这个区域的垃圾回收主要目标是常量池的回收和对类型的卸载。
JVM规范规定,这个区域会抛出 OutOfMemoryError 异常:当方法区无法满足内存分配需求时抛出。
运行时常量池
运行时常量池 是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
运行时常量池的另一个重要特征是具备动态性,除了Class文件中常量池的内容进入方法区常量池外,运行期间也可能将新的常量放入池中,比如String类的intern()。
这部分会抛出 OutOfMemoryError 异常:当常量池无法再申请到内存时会抛出。
堆
Java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建。大多数应用中,堆事JVM内存中最大的一块。按照JVM规范,所有的对象实例以及数组都要在堆上分配,但是随着技术的发展,这已经不是绝对的了。
Java堆是垃圾收集器管理的主要区域,被称为GC堆,按照分带收集算法(现在基本是),堆分为新生代和老年代。从内存非配的角度,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。
这里存放的都是对象实例。
堆的大小可通过 -Xmx 和 -Xms 控制。
这里会抛出 OutOfMemoryError:堆中没有内存完成实例分配,并且堆也无法再扩展时。
总结
简单的归类,Java运行时内存区域中:
属于线程私有的区域是:程序计数器、虚拟机栈、本地方法栈;
属于线程共享的区域是:堆、方法区(包括运行时常量池)。