深入理解 JVM 之 JVM 内存结构

栏目: Java · 发布时间: 5年前

内容简介:程序计数器是当前线程所执行的字节码的行号指示器,它会指出下一条将要执行的指令的地址,字节码解释器就是通过改变计数器的值来选取程序接下来执行的操作。程序计数器是线程私有的一小块内存,每条线程都要有一个独立的程序计数器,以使线程切换后恢复到正确的执行位置。

Java 虚拟机在运行 Java 程序 时,把它所管理的内存划分为若干个不同的数据区域,主要包括以下五个部分:程序计数器、 Java 堆、 Java 虚拟机栈、方法区和本地方法栈。

深入理解 JVM 之 JVM 内存结构

JVM 内存结构

程序计数器

程序计数器是当前线程所执行的字节码的行号指示器,它会指出下一条将要执行的指令的地址,字节码解释器就是通过改变计数器的值来选取程序接下来执行的操作。

程序计数器是线程私有的一小块内存,每条线程都要有一个独立的程序计数器,以使线程切换后恢复到正确的执行位置。

Java
native

它也是唯一一个不会出现 OutOfMemoryError 的内存区域。

Java 虚拟机栈

与程序计数器一样, Java 虚拟机栈也是线程私有的,在线程创建时 Java 栈会被创建,每个方法在在执行的同时都会创建一个栈帧,用于存放局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直至执行完成,都对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

一般所谓的“栈”,指的是虚拟机栈中局部变量表部分,其中存放了各种基本数据类型( 8 种),对象引用( reference 类型) 和 returnAddress 类型。局部变量表所需的空间在编译期就已经确定并完成分配,在方法运行期间不会被改变。

Java 虚拟栈中可能出现两种异常:

StackOverflowError
OutOfMemoryError

本地方法栈

本地方法栈与 Java 虚拟机栈的作用类似,区别是 Java 虚拟机栈为虚拟机执行 Java 方法服务,而本地方法栈为虚拟机执行 Native 方法服务。有的虚拟机(例如 HotSpot 虚拟机)直接把本地方法栈和 Java 虚拟机栈合并在一起。

本地方法栈也可能会抛出 StackOverflowErrorOutOfMemoryError 异常。

Java 堆

Java 堆是是虚拟机中最主要的内存区域。它为线程共享,在虚拟机启动时创建,几乎所有的对象实例都存储在 Java 堆中。

Java 堆也被称作 "GC" 堆。从内存回收角度看,可分为新生代和老年代。而新生代又可分为 Eden 区、 From Survivor 区、 To Survivor 区等。

Java 堆的实现,既可以实现为固定的,也可以是扩展的。当前虚拟机都按照可扩展来实现,通过 -Xmx-Xms 控制堆大小。

如果堆中没有内存并且也无法再扩展时,会抛出 OutOfMemeoryError 异常。

方法区

方法区与 Java 堆一样,为线程共享。用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。也叫作 Non-Heap (非堆)。

如果方法区无法满足内存分配需求,会抛出 OutOfMemoryError 异常。

运行时常量池

运行时常量池是方法区的一部分。 Class 文件中的常量池用于编译期生成的各种字面量和符号引用,这部分内容在类加载后被存入运行时常量池。

动态性是运行时常量池相对于 Class 文件常量池的一个重要特征,即不要求常量一定只有编译期才能产生,运行期间也可能将新的常量放入池中。

运行时常量池受到方法区内存的限制,如果常量池无法再申请内存,就会抛出 OutOfMemoryError 异常。

直接内存

直接内存并不由 JVM 管理,它是利用 Native 函数库在 Java 堆外申请分配的内存区域,可以避免在 Java 堆和 Native 堆中复制数据以提高性能。

例如 NIO 中的 DirectByteBuffer 就可以作为这块内存的引用进行操作直接内存。

永久代与元空间

有时会看到方法区被称为永久代,其实两者有着本质的区别。方法区是 JVM 规范中的定义,而永久代是 JVM 规范的一种实现,并且只有在 HotSpot 虚拟机中如此,其他虚拟机中没有永久代的说法。

JDK1.6 之前, HotSpot 虚拟机把 GC 分代收集扩展至方法区,或者说使用永久代实现方法区。不过永久代有 -XX:MaxPermSize 的上限,很容易遇到内存溢出问题。

所以在 JDK1.7 中,将部分数据已经转移 Java HeapNative Heap 中,例如:将原本放在永久代中的字符串池和类的静态变量移出到 Java Heap 中,将符号引用转移到 Native Heap 中。但永久代仍然存在,并没有移除。

JDK1.8 中,取消了永久代,代替为元空间实现,它也是 JVM 规范中方法区的一种实现。不过它与永久代最大的不同是:元空间并不在虚拟机中,而是将元空间放到本地内存中。所以默认情况下,它只受本地内存的限制,可以通过 -XX:MetaspaceSize 参数设置初始空间大小,默认没有最大空间限制。

常见的 OOM 及原因

Java 中的 OOM 指的就是 java.lang.OutOfMemoryError 异常。主要有以下几种:

java.lang.OutOfMemoryError:Java heap space

Java 堆中主要用于存放各种对象实例。当堆中没有足够的空间分配给新对象时,或者说达到了堆空间设置的最大空间限制,则会抛出此异常。

引起内存溢出的原因主要有:

  • 流量访问量大,超过设置的堆空间大小;
  • 内存泄露,不能被回收的对象消耗过多堆空间;

java.lang.OutOfMemoryError:Permgen space

JDK7 中, HotSpot 虚拟机使用永久代实现方法区,永久代较小,而且回收效率较低,很容易出现内存溢出。

因此, JDK8 取消了永久代,使用元空间来实现方法区,存放在本地内存中。

java.lang.OutOfMemoryError:Metaspace

方法区主要存储类的元信息, HotSpot 元数据区。当元空间没有足够的空间分配给加载的类时,会抛出此异常。

引起元数据区空间不足的原因主要有:

jsp

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learning PHP, MySQL, JavaScript, and CSS

Learning PHP, MySQL, JavaScript, and CSS

Robin Nixon / O'Reilly Media / 2012-9-3 / USD 39.99

If you're familiar with HTML, you can quickly learn how to build interactive, data-driven websites with the powerful combination of PHP, MySQL, and JavaScript - the top technologies for creating moder......一起来看看 《Learning PHP, MySQL, JavaScript, and CSS》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具