⛳ JVM内存模型和五大结构详解

JVM内存模型目录

  • ⛳ JVM内存模型和五大结构详解
    • 一,什么是JVM
    • 二,为什么需要JVM?
    • 三,JVM内存模型
      • 3.1,堆(Heap)
        • Java 堆的分类:
        • TLAB(Thread Local Allocation Buffer,线程私有分配缓冲区):
      • 3.2,方法区(Method Area)
        • 1,类型信息
        • 2,域信息(Field)成员变量
        • 3,方法(Method)信息
      • 3.3,虚拟机栈(JVM Stack)
      • 3.4,本地方法栈(Native Stack)
      • 3.5,程序计数器(PC Register)
    • ⭐ 四,JVM 内存模型小结

⛳ JVM内存模型和五大结构详解

⛳ JVM内存模型和五大结构详解_第1张图片

一,什么是JVM

JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,有着自己完善的硬件架构,如处理器,堆栈等,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。

二,为什么需要JVM?

Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可以在多个平台上不加修改的运行。

Java文件必须先通过一个叫javac的编译器,将代码编成class文件,然后通过JVM把class文件解释成各个平台可以识别的机器码,最终实现跨平台运行代码。

⛳ JVM内存模型和五大结构详解_第2张图片

三,JVM内存模型

JVM 内存模型可以分为两部分,堆和方法区是所有线程所共有的,而虚拟机栈,本地方法栈和程序计数器则是线程私有的。

⛳ JVM内存模型和五大结构详解_第3张图片

⛳ JVM内存模型和五大结构详解_第4张图片

在JVM1.8中,图中的 方法区为元数据区下面谈一谈五个区域的作用。

3.1,堆(Heap)

JVM 管理的最大的一块内存区域,存放着对象的实例,是线程共享区。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。可以通过参数 -Xmx -Xms 来指定运行时堆内存的大小,如果在Java堆中没有内存完成实例分配,并且堆也无法再 扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

堆是垃圾收集器管理的主要区域,因此也被称为 “GC(Garbage Collected Heap)堆” 。

Java 堆的分类:

从内存回收的角度上看,可分为新生代(Eden空间,From Survivor空间、To Survivor空间)及老年代(Tenured Gen)。

⛳ JVM内存模型和五大结构详解_第5张图片

​ 从内存分配的角度上看,为了解决分配内存时的线程安全性问题,线程共享的JAVA堆中可能划分出多个线程私有的分配缓冲区(TLAB)。

TLAB(Thread Local Allocation Buffer,线程私有分配缓冲区):

从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区 (Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。不过无论从什么角度,无论如 何划分,都不会改变Java堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java 堆细分的目的只是为了更好地回收内存,或者更快地分配内存。

虚拟机为新生对象分配内存时,存在可能出现正在给对象A分配内存,指针还未修改,对象B又同时使用原来的指针分配内存的情况(指针用于划分内存使用空间和空闲空间)。TLAB 的存在就是为了解决这个问题:每个线程在Java堆中预先分配一小块内存 TLAB,线程需要分配内存时首先在自己的TLAB上进行分配,若TLAB用完并分配新的TLAB时,再加同步锁定,这样就大大提升了对象内存分配的效率。

3.2,方法区(Method Area)

其实方法区是在JDK1.8以前的版本里存在的一块内存区域主要就是存放从class文件里加载进来的类的,而且常量池也是在这块区域内的。

但是在JDK1.8之后,这块区域摇身一变,换了名字,叫做“Metaspace”,翻译过来就是“元数据空间”的意思,当然它只是改了个名,实现的功能是没变的。

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

⛳ JVM内存模型和五大结构详解_第6张图片

1,类型信息

对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:
①这个类型的完整有效名称(全名=包名.类名)
②这个类型直接父类的完整有效名(对于interface或是java.lang.0bject,都没有父类)
③这个类型的修饰符(public, abstract,final的某个子集)
④这个类型直接接口的一个有序列表

2,域信息(Field)成员变量

JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
域的相关信息包括:域名称、域类型、域修饰符(public, private,protected,static,final, volatile, transient的某个子集)

3,方法(Method)信息

当有多个线程都用到一个类的时候,而这个类还未被加载,则应该只有一个线程去加载类,让其他线程等待。

JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:

  • 方法名称
  • 方法的返回类型(或void) · 方法参数的数量和类型(按顺序)
  • 方法的修饰符(public, private,protected,static, final,synchronized,native,abstract的一个子集)
  • 方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native方法除外)

⛳ JVM内存模型和五大结构详解_第7张图片

方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。

3.3,虚拟机栈(JVM Stack)

虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈,每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用。

虚拟机栈的作用:主管Java程序的运行,它保存方法的局部变量、部分结果,并参与方法的调用和返回。

每个方法被执行的时候都会创建一个”栈帧”,用于存储局部变量表(包括参数)、操作栈、方法出口等信息。

每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

栈帧(Stack Frame) 是用于虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈的基本元素,栈帧由局部变量区、操作数栈等组成,如下图所示:

⛳ JVM内存模型和五大结构详解_第8张图片

每一个栈帧包含的内容有局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译代码时,栈帧需要多大的局部变量表,多深的操作数栈都可以完全确定的,并写入到方法表的code属性中。

这个内存区域规定了两类异常状况:如果线程请求的栈深度大于虚 拟机所允许的深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展,当栈扩 展时无法申请到足够的内存会抛出OutOfMemoryError异常

3.4,本地方法栈(Native Stack)

与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机 栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native) 方法服务。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失 败时分别抛出StackOverflowError和OutOfMemoryError异常。

普通开发可以忽略二者的区别,JDK1.8 HotSpot虚拟机直接就把本地方法栈和虚拟机栈合二为一。

3.5,程序计数器(PC Register)

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的 字节码的信号指示器。字节码解释器工作时就是通过改变这个计数器 的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处 理、线程恢复等基础功能都需要依赖这个计数器来完成。 由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一 个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因 此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程 之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

此内存区域是唯 一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。

⭐ 四,JVM 内存模型小结

Java Virtual Machine (JVM) 内存模型是指在 Java 程序执行过程中,JVM 是如何管理内存的一种抽象描述。它定义了 JVM 在运行 Java 程序时,如何划分和使用内存区域,以及各个区域的作用和生命周期。以下是 JVM 内存模型的主要组成部分的小结:

  1. 程序计数器(Program Counter)
    • 是一块较小的内存区域,用于存储当前线程执行的字节码指令地址。
    • 在线程切换时,程序计数器会被保存和恢复,保证线程能够正确地从上次执行的位置继续执行。
  2. Java 虚拟机栈(JVM Stack)
    • 每个线程在运行时都会创建一个私有的 Java 虚拟机栈。
    • 用于存储方法的局部变量、部分结果和操作数栈。
    • 方法调用和返回都会在虚拟机栈中进行入栈和出栈操作。
  3. 本地方法栈(Native Method Stack)
    • 类似于 JVM 栈,但用于执行 Native 方法(由本地代码实现的方法)。
  4. Java 堆(Java Heap)
    • 是 JVM 中最大的一块内存区域。
    • 用于存储对象实例和数组,被所有线程共享。
    • Java 堆在 JVM 启动时创建,其大小可以通过命令行参数进行调整。
  5. 方法区(Method Area)
    • 用于存储已加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    • 方法区也被称为永久代(Permanent Generation),在较新的 JVM 版本中,被 Metaspace 取代。
  6. 运行时常量池(Runtime Constant Pool)
    • 是方法区的一部分,用于存储编译期生成的各种字面量和符号引用。
    • 运行时常量池中的信息可在运行时被动态地修改,例如动态生成新的类。
  7. 直接内存(Direct Memory)
    • 不是 JVM 内存管理的一部分,但也与 Java NIO 密切相关。
    • 直接内存通过 Native 方法直接分配在操作系统的内存中,而不受 Java 堆大小限制。

JVM 内存模型的具体实现可能因不同的 JVM 版本和厂商而有所不同。随着 JVM 的不断发展,一些细节可能会有所改变,例如使用 Metaspace 替代永久代等。因此,在实际应用中,要注意 JVM 的具体配置和版本,以充分利用内存,并避免出现内存相关的性能问题。

你可能感兴趣的:(Java基础知识,JVM,java,jvm)