JVM内存模型

35 阅读6分钟

JVM内存

一 概述

内存 是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行.

在JVM自动内存管理机制,开发人员不需要去考虑内存的delete/free操作,不容易出现内存溢出和泄露问题。

二 运行时数据区域/内存布局

1.8之前和之后略有区别,主要体现在为1.8之前存在方法区(Method Area),1.8之后去掉方法区,增加元空间(MetaSpace)

Java内存布局

2.1 虚拟机栈

  1. Java 虚拟机栈(Java Virtual Machine Stacks)也就是我们平时所说的 栈内存,或者指的就是虚拟机栈中的 局部变量表 部分。
    虚拟机栈描述的是Java方法的内存模型:即每个方法在执行的同时都会创建一个 栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。所以虚拟机栈是线程私有的,生命周期与线程相同。

  2. 栈针(Stack Frame) :用于支持虚拟机进行方法调用和方法执行的数据结构。每一个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。

  3. 局部变量表:存放编译期方法内部的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference)类型等。
    局部变量表所需空间是在编译期间完成分配的,当进入方法,该方法需要的栈针分配的空间是固定,在方法运行期间不会改变.

操作数栈:Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。当JVM为方法创建栈帧的时候,在栈帧中为方法创建一个操作数栈,保证方法内指令可以完成工作。

动态链接: 每个栈帧中包含一个在常量池中对当前方法的引用, 目的是支持方法调用过程的动态连接。

方法返回地址: 退出的两种形式,1、正常退出;2、抛出异常。

  1. 虚拟机栈在JVM中存在两种异常
  • StackOverflowError:若线程请求的栈深度大于当前虚拟机所允许的深度,抛出此异常
  • OutOfMemoryError:若虚拟机栈可动态扩展,当扩展时无法申请到足够的内存,抛出此异常

2.2 本地方法栈

本地方法栈Native Method Stack)与虚拟机栈的作用是相似。区别是:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。

此外,对于此区域出现的异常,和虚拟机栈是相同的。

2.3 程序计数器

程序计数器(Program Counter Register)是当前线程所执行的字节码的行号指示器,它占用一块较小的内存空间。字节码解释器通过改变该计数器的值来选取下一条需要执行的字节码指令,其中包括分支、循环、跳转、异常处理、线程恢复等基础功能。

Java多线程是处理器的时间片转轮的策略的实现的,任一确定时刻,一个处理器只执行一条线下中的命令,为了切换线程后可恢复到正确的执行位置,程序计数器为线程私有。

程序计数器是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。

2.4 堆

  1. 堆(Heap)是JVM最重要的也是最大的一块内存,在虚拟机启动时创建。对于堆来说,它可以位于物理上不连续而逻辑上连续的内存空间中。在实际情况下也可以通过调整虚拟机参数的方式对堆进行扩展,即通过 -Xmx 和 -Xms 控制。

  2. 该区域的主要作用: 为对象实例及数组分配内存,几乎存储了所有对象实例。同时也是垃圾回收器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap)。

    Java堆还可以细分为:新生代和老年代。再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。具体如下图: 堆Heap详细分布1.8后去除永生代,用元空间替代。

    查看虚拟机默认配置命令:

java -XX:+PrintFlagsFinal -version

2.5 方法区(Method Area)

  1. 方法区(Method Area)用于存储已被虚拟机加载的 类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。

  2. 方法区也被成为永生代,二者关系分析起来比较容易混淆,引用下面个人认为比较好的释义:

《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。 也就是说,永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。

  1. 方法区或者称为永生代,在1.8中被去除。并用元空间(Metaspace)替换。

2.5 元空间(Metaspace)

  1. 元空间替换方法区的原因:
  1. 根据上面的原因方法区被移除,方法区中类元信息、字段、静态属性、方法、常量等移至元空间,并在1.7时将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
  2. 元空间和方法区本质类似,主要区别在于:元空间使用本地内存,方法区存在于虚拟机中。 所以元空间的大小取决于本地内存的容量。

元空间对应的JVM调参:

参数作用
-XX:MaxMetaspaceSize分配给Metaspace 的最大值,超过此值就会触发Full GC,此值默认没有限制,但应取决于系统内存的大小。JVM会动态地改变此值。
-XX:MinMetaspaceFreeRatio在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集
-XX:MaxMetaspaceFreeRatio在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集
-XX:MetaspaceSize分配给Metaspace(以字节计)的初始大小

2.6 直接内存(Direct Memory)

直接内存并不属于Java内存的一员,但这部分也经常被使用并也会出现OutOfMemoryError 异常。
虽然直接内存不受Java堆大小的限制,但还是会受到本机总内存的大小及处理器寻址空间的限制。如果内存区域总和大于物理内存的限制,也会出现OOM。


参考: