运行时数据区域
JVM所管理的内存会包括以下运行时数据区: 线程隔离的:虚拟机栈、本地方法栈、程序计数器; 由所有线程共享的:堆、方法区;
不在运行时数据区的:执行引擎、本地库接口、本地方法库。
程序计数器
用于指示当前线程所执行的字节码的行号,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖它完成。
每条线程都有一个独立的程序计数器(线程私有),彼此之前独立存储,互不影响。
虚拟机栈
为Java方法(字节码)服务,每个方法被执行时,JVM都会同步创建一个栈帧,方法被调用直至执行完毕的过程,对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。
局部变量表存放了:
- JVM基本数据类型(boolean、byte、char、short、int、float、long、double,编译期可知);
- 对象引用(指向对象起始地址的引用指针或代表对象的句柄,不是对象本身)
- returnAddress类型(指向一条字节码指令的地址)。
方法运行期间局部变量表的大小不会改变。
HotSpot虚拟机的栈容量不可动态扩展。
本地方法栈
为本地方法(Native方法)服务。
并非所有Java虚拟机都有独立的本地方法栈,如HotSpot将本地方法栈和虚拟机栈合二为一。
堆
用于存放对象实例(不是对象引用),在JVM启动时创建,是JVM内存中最大的一块,被所有线程共享。
Java堆可处于物理上不连续的内存空间中,但是逻辑上被视为连续。
Java堆是GC主要管理的内存区域。
当前主流的JVM都是按照可扩展来实现Java堆的,可通过参数-Xmx和-Xms设定。
方法区
用于存储已被JVM加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据, 各个线程共享。
JDK6及以前的HotSpot使用永久代实现方法区,大小具有限制。
JDK7的HotSpot将字符串常量池、静态变量等从永久代移出。
JDK8及以后的HotSpot完全废弃永久代,改用在本地内存中实现的元空间代替,最大内存为系统可用内存。
不需要物理上连续的内存、可以选择固定大小或可扩展、可以选择不实现GC。
运行时常量池
方法区的一部分,用于存放在类加载后,于编译期生成的字面量与符号引用。
字面量与符号引用在类加载前存放在常量池表中,常量池表则存放在class文件中。(同时class文件中还包括类的版本、字段、方法、接口描述等信息)。
不仅预置入class文件中常量常量池表中的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入常量池。
内存溢出异常
会出现 StackOverflowError 异常的情况:
- 线程请求的栈深度大于虚拟机栈所允许的深度;
- 线程请求的栈深度大于本地方法栈所允许的深度。
会出现 OutOfMemoryError 异常的情况:
- 虚拟机栈或本地方法栈可扩展,无法满足扩展时请求的内存;
- 线程虚拟机栈或本地方法栈使用内存过大(未超过深度),无法满足;
- 堆中没有足够的内存完成实例分配,且无法再扩展;
- 方法区无法满足新的新的内存分配需求(包括常量池);
- 各个内存区域总和大于物理内存限制,导致动态扩展时出现异常。