JVM 基础:内存分区

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Gdeer/article/details/88095154

运行时数据区

Java 虚拟机运行时数据区包括:

堆、方法区;
虚拟机栈、本地方法栈、程序计数器

在这里插入图片描述

1 程序计数器

程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。代码的分支、循环、跳转、异常处理、线程切换等操作都需要用到程序计数器。

每个线程都需要一个独立的程序计数器,保证各不影响,这类内存区域称为“线程私有”的内存。

程序计数器是唯一一个不会发生 OOM 的内存区域。

2 Java 虚拟机栈

Java 虚拟机栈也是线程私有的,它的生命周期与线程相同。

每个方法在执行时,都会创建一个栈帧用于存储局部变量表操作数表动态链接方法出口等信息。方法从调用到执行完成,就对应栈帧在 Java 虚拟机栈中入栈出栈的过程。栈帧的生命周期与它对应的方法相同。

局部变量表存放了编译期可知的各种基本数据类型(byte、short、int、long、float、double、char、boolean),对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)。

局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

抛出异常:

当栈深度大于虚拟机允许的深度,将抛出 StackOverflowError 异常。当虚拟机栈扩展时,如果没有申请到足够的内存,将抛出 OutOfMemoryError 异常。

3 本地方法栈

本地方法栈与虚拟机栈非常相似,虚拟机栈为 java 方法服务,本地方法栈为 Native 方法服务,

虚拟机规范中没有对本地方法栈强制规定,所以本地方法栈的实现比较灵活,如 HotSpot 虚拟机就将虚拟机栈和本地方法栈合而为一。

本地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常。

4 Java 堆

Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。几乎所有的对象实例都存放在这里(新技术的发展、优化,有的对象可以不存放在堆里)。

  • 从内存回收的角度,Java 堆可以分为新生代(Eden 空间Form Survivor 空间To Survivor 空间)、老年代。
  • 从内存分配的角度,Java 堆可以分为多个线程私有的分配缓冲区。

可以通过 -Xmx-Xms 来控制 Java 堆的大小。如果内存不足且无法扩展时,会抛出 OOM 异常。

-Xmx:memory max
-Xms:memory start

5 方法区

方法区也是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据。

如何实现方法区属于虚拟机实现细节,不受虚拟机规范约束。早期的 HotSpot 虚拟机用 GC 分代算法中的永久代来实现方法区。但这样方法区的内存就限制在了-XX:MaxPermSize声明的数值里,超出就会OOM,而永久代的GC条件非常苛刻,这会很容易导致 OOM。

在 JDK1.7 的 HotSpot 中,已经把原来放在永久代的字符串常量池移出到了 Java 堆中。
在 JDK1.8 中,已经移除了永生代。将类元数据放到 Native Memory 中,将常量池和静态变量放到 Java 堆里。见 JVM 移除永久代

6 运行时常量池

运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

字面量:文本字符串、声明为 final 的常量值。
符号引用:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

Java 语言并不要求常量一定只有在编译期才能产生,也就是并非预置入 class 文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,如 String.intern 方法。

运行吃常量池也可能抛出 OOM 异常。

7 直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域。但这部分内存也被频繁地使用,也可能抛出 OOM,所以一起讲解。

在 JDK 1.4 中加入了 NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓存(Buffer)的 I/O 方式。它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。

这部分内存不受到 Java 堆大小的影响,但还会受到本机总内存影响。如果配置时忽略了它,很可能导致OOM。

猜你喜欢

转载自blog.csdn.net/Gdeer/article/details/88095154