JVM内存区域

87 阅读3分钟

运行时数据区域

JVM所管理的内存会包括以下运行时数据区: 线程隔离的:虚拟机栈、本地方法栈、程序计数器; 由所有线程共享的:堆、方法区;

不在运行时数据区的:执行引擎、本地库接口、本地方法库。

image.png

程序计数器

用于指示当前线程所执行的字节码的行号,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖它完成。

每条线程都有一个独立的程序计数器(线程私有),彼此之前独立存储,互不影响。

虚拟机栈

为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 异常的情况:

  • 虚拟机栈或本地方法栈可扩展,无法满足扩展时请求的内存;
  • 线程虚拟机栈或本地方法栈使用内存过大(未超过深度),无法满足;
  • 堆中没有足够的内存完成实例分配,且无法再扩展;
  • 方法区无法满足新的新的内存分配需求(包括常量池);
  • 各个内存区域总和大于物理内存限制,导致动态扩展时出现异常。