Java-JVM 运行时内存结构(Run-Time Data Areas)

Java 虚拟机定义了在程序执行期间使用的各种运行时数据区域。

其中一些数据区域所有线程共享,在 Java 虚拟机(JVM)启动时创建,仅在 Java 虚拟机退出时销毁。

还有一些数据区域是每个线程的。线程数据区域是在线程启动时创建,线程结束时销毁。

 

一、运行时数据区划分(JDK8)

1、The pc Register(PC 寄存器、程序计数器)

2、Java Virtual Machine Stacks(Java 虚拟机栈、Java 栈)

3、Native Method Stacks(本地方法栈,C栈)

4、Heap(堆)

5、Method Area(方法区,JDK8 中的实现叫元数据区(本地内存中),JDK7 中的实现叫永久代(JVM中))

6、Run-Time Constant Pool(运行时常量池,方法区的一部分)

 

二、区划分详情

2.1.The PC Register(PC 寄存器)

每个 JVM 线程都有自己的 pc 寄存器(内存为线程私有,随着线程的创建而创建,线程的结束而销毁)。

在任何时候,每个 JVM 线程都在执行单个方法的代码,即该线程的当前方法(字节码解释器通过改变程序计数器来选取下一条需要执行指令,从而实现代码的流程控制,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成)。

如果该方法不是 native,则 pc 寄存器包含当前正在执行的 JVM 指令的地址(线程切换就知道上次线程执行到哪了)。

如果该方法是 native,则 pc 寄存器为 Undefined(不会 OutOfMemoryError)。

 

2.2.Java Virtual Machine Stacks(Java 虚拟机栈)

描述 Java 方法执行的内存模型。

每个 JVM 线程都有一个私有 JVM 栈,与线程同时创建(内存为线程私有,随着线程的创建而创建,线程的结束而销毁)。

JVM 栈存储 frames (栈帧)。方法调用和返回对应压栈和出栈(栈顶的栈帧是当前正在执行的活动栈,也就是当前正在执行的方法,PC 寄存器也会指向这个地址,只有这个活动的栈帧的本地变量可以被操作数栈使用)。

由于除了压栈和出栈之外,永远不会直接操作 JVM 栈,JVM 栈的内存不需要是连续的。

JVM 规范允许 JVM 栈具有固定大小,也可以根据计算的需要动态扩展和收缩(通过 -Xss 控制)。 

以下异常与 JVM 栈有关:

如果不可以动态扩展 Java 虚拟机栈,当线程中的方法调深度用超过 Java 虚拟机栈最大深度时,会抛出 StackOverflowError 异常(出现 StackOverFlowError 时,内存空间可能还有很多)。

如果可以动态扩展 Java 虚拟机栈,当线程尝试进行扩展但可使内存不足以实现扩展,或者可使内存不足以为新线程创建初始 Java 虚拟机堆栈时,会抛出 OutOfMemoryError 异常。

 

2.3.Native Method Stacks(本地方法栈)

描述本地方法运行过程的内存模型。

JVM 可以使用常规栈来支持 native 方法(用 Java 编程语言以外的语言编写的方法,执行也会创建栈帧)。

无法加载 native 方法,并且本身不依赖于传统堆栈的 JVM, 不需要提供本地方法栈。如果提供,则通常在每个线程创建时分配本地方法栈。

本地方法栈具有固定大小,也可以根据计算的需要动态扩展和收缩。

以下异常与本地方法栈有关:

如果不可以动态扩展本地方法栈,当线程中的计算需要比允许的本地方法栈更大,则会抛出 StackOverflowError 异常。

如果可以动态扩展本地方法栈,当尝试进行本地方法栈扩展,但可使内存不足,或没有足够的内存可用于为当前前程创建初始本地方法栈,则会抛出 OutOfMemoryError 异常。

 

2.4.Heap(堆)

堆是运行时数据区,从中分配所有类实例和数组的内存(JVM 中内存最大的一块,被所有线程共享,需要注意同步问题)。

堆是在 JVM 启动时创建的。

堆中对象的存储由垃圾收集器(GC,自动存储管理系统回收,对象永远不会被显式释放。

JVM 没有特定类型的 GC,可以根据实现者的系统要求选择存储管理技术。

堆可以具有固定大小,也可以根据计算的需要进行扩展(通过 -Xmx 和 -Xms 控制)。堆的内存不需要是连续的。

以下异常情况与堆有关:

如果计算需要的堆量超过自动存储管理系统可用的堆,则会抛出 OutOfMemoryError 异常。

 

2.5.Method Area(方法区)

方法区在所有 JVM 线程之间共享。方法区是在 JVM 启动时创建的。方法区在逻辑上是堆的一部分,但可选择不实现垃圾回收。

方法区存储类结构,如运行时常量(Run-Time Constant Pool),字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化以及接口初始化中使用的特殊方法

JVM 规范未规定方法区的位置或用于管理编译代码的策略。

方法区可以是固定大小的,也可以根据计算的需要进行扩展。方法区的内存不需要是连续的。

以下异常与方法区有关:

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

 

2.6.Run-Time Constant Pool(运行时常量池)

运行时常量池是方法区的一部分。

Class 文件中的常量池(constant_pool Table),用于存放编译期生成的各种字面量和符号引用,这部分在类加载后进入方法区的运行时常量池中。

在运行期间,也可以向常量池中添加新的常量。如 String 类的 intern() 方法。

每个运行时常量池都是从 JVM 的方法区分配的

以下异常与类或接口的运行时常量池的构造有关:

在创建类或接口时,如果运行时常量池的构造需要的内存比 JVM 方法区中可用的内存多,会抛出 OutOfMemoryError 异常

 

三、直接内存(堆外内存)

不是 JVM 运行时数据区的一部分,但这部分内存也会被频繁的使用,也可能导致 OutOfMemoryError 异常。

JDK 1.4 中新加入了 NIO 类,通过调用本地方法直接分配 Java 虚拟机之外的内存,然后通过一个存储在堆中的 DirectByteBuffer 对象直接操作该内存。

避免了 Java 堆和 Native 堆来回交换数据的时间,更高效。

 

四、大概划分图(JDK8-HotSpot)


https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5

http://www.hollischuang.com/archives/2509

https://github.com/doocs/jvm/blob/master/docs/01-jvm-memory-structure.md

https://my.oschina.net/u/3471412/blog/3001024

https://blog.csdn.net/21aspnet/article/details/88772421

posted @ 2019-05-21 11:58  江湖小小白  阅读(1977)  评论(0编辑  收藏  举报