图解 JVM 内存结构

article/2025/10/10 11:39:59

文章目录

    • JVM 内存结构
      • JVM包含哪几部分?
      • 在内存中 java 代码的执行的流程
      • Java内存分布:各组件详细说明
        • 1. 程序计数器
        • 2. Java虚拟机栈
        • 3. 本地方法栈
        • 4. Java堆
        • 5. 方法区
        • 6. 运行时常量池
        • 7. 直接内存
    • 总结问题
      • 那些区域会发生内存溢出?
      • 类存放在哪里?
      • 局部变量存放在哪里?

JVM 内存结构

先别扯内存了,咱先说说 JVM 是结构吧,我懂大家。

img

JVM包含哪几部分?

JVM 主要由四大部分组成:ClassLoader(类加载器),Runtime Data Area(运行时数据区,内存分区),Execution Engine(执行引擎),Native Interface(本地库接口),下图可以大致描述 JVM 的结构。

image-20220805173454039

JVM 是执行 Java 程序的虚拟计算机系统,那我们来看看执行过程:首先需要准备好编译好的 Java 字节码文件(即class文件),计算机要运行程序需要先通过一定方式(类加载器)将 class 文件加载到内存中(运行时数据区),但是字节码文件是JVM定义的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解释器(执行引擎)将字节码翻译成特定的操作系统指令集交给 CPU 去执行,这个过程中会需要调用到一些不同语言为 Java 提供的接口(例如驱动、地图制作等),这就用到了本地 Native 接口(本地库接口)。

ClassLoader(类加载器):负责加载字节码文件即 class 文件,class 文件在文件开头有特定的文件标示,并且ClassLoader 只负责class 文件的加载,至于它是否可以运行,则由 Execution Engine 决定。

Runtime Data Area(运行时数据区):是存放数据的,分为五部分:Stack(虚拟机栈),Heap(堆),MethodArea(方法区),PC Register(程序计数器),Native Method Stack(本地方法栈)。几乎所有的关于 Java 内存方面的问题,都是集中在这块。

Execution Engine(执行引擎):执行引擎,也叫 Interpreter。Class 文件被加载后,会把指令和数据信息放入内存中,Execution Engine 则负责把这些命令解释给操作系统,即将 JVM 指令集翻译为操作系统指令集。

Native Interface(本地方法库):负责调用本地接口的。他的作用是调用不同语言的接口给 JAVA 用,他会在Native Method Stack 中记录对应的本地方法,然后调用该方法时就通过 Execution Engine 加载对应的本地 lib。原本多用于一些专业领域,如JAVA驱动,地图制作引擎等,现在关于这种本地方法接口的调用已经被类似于Socket通信,WebService等方式取代。

干说不太行,我们拿一段代码的执行解释说明一下各个组件的简单功能:

在内存中 java 代码的执行的流程

结合一段 java 代码的执行理解内存划分

在main.java文件里有如下源代码:

public class Main{public static void main(String[] args) {Student student  = new Student();student.study();    // 学习方法student.hashCode();student=null;}
}

首先编译器会执行 javac 命令将源代码编译为字节码(main.class文件)

这个文件我们在IDEA中是能找到的:

image-20220805183631062

再执行 java 命令

1.创建 JVM,调用类加载子系统加载 class,将类的信息存入方法区

PS:方法区用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据

image-20220805184348436

2.创建 main 线程,使用的内存区域是 JVM 虚拟机栈,开始执行 main 方法代码

PS:Java 虚拟机栈为main主线程分配内存

image-20220805183930935

3.如果遇到了未见过的类,会继续触发类加载过程,同样会存入方法区

比如说这里的Student类,之前没有加载到方法区:

image-20220805184935805

4.需要创建对象,会使用内存来存储对象

image-20220805185050511

5.不再使用的对象,会由垃圾回收器在内存不足时回收其内存

image-20220805185718970

6.调用方法时,方法内的局部变量、方法参数所使用的是 JVM 虚拟机栈中的栈帧内存

PS:对象变量也是,变量引用的具体内容在堆中,变量本身存在Java虚拟机栈

image-20220805185227498

7.调用方法时,先要到方法区获得到该方法的字节码指令,由解释器将字节码指令解释为机器码执行

8.调用方法时,会将要执行的指令行号读到程序计数器,这样当发生了线程切换,恢复时就可以从中断的位置继续

9.对于非 java 实现的方法调用,使用内存称为本地方法栈

PS:对于 Oracle 的 Hotspot 虚拟机实现,不那么区分虚拟机栈和本地方法栈,基本都是由虚拟机栈调用方法

10.对于热点方法调用,或者频繁的循环代码,由 JIT 即时编译器将这些代码编译成机器码缓存,提高执行性能

比如说上面的Student的study被调用了一万次,JVM就会吧这个方法放入即时编译器中,下次就不会在进过解释器解释,直接编译运行

image-20210831165728217

Java内存分布:各组件详细说明

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域。

image-20220805190112299

其中分为两个数据区:

  • 线程共享:方法区、堆、执行引擎、本地库接口
  • 线程私有:虚拟机栈、本地方法栈、程序计数器

下面根据概念用法也深入理解下为什么分成共享与私有

1. 程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。

在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。

因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存

如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefifined)。

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

2. Java虚拟机栈

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,它的生命周期与线程相同。

虚拟机栈描述的是Java方法执行的线程内存模型每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧[插图](Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。

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

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

3. 本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。所以本地方法栈也是线程私有的

《Java虚拟机规范》对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机(譬如Hot-Spot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。

与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflflowError和OutOfMemoryError异常。

4. Java堆

对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。

Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建

此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存。

PS:在《Java虚拟机规范》中对Java堆的描述是:“所有的对象实例以及数组都应当在堆上分配”,而这里笔者写的“几乎”是指从实现角度来看,随着Java语言的发展,现在已经能看到些许迹象表明日后可能出现值类型的支持,即使只考虑现在,由于即时编译技术的进步,尤其是逃逸分析技术的日渐强大,栈上分配、标量替换优化手段已经导致一些微妙的变化悄然发生,所以说Java对象实例都分配在堆上也渐渐变得不是那么绝对了。

就像那句话,现在这个世道,太绝对的绝对,绝对是错的,哈哈哈哈,这句话是也挺绝对的img

根据《Java虚拟机规范》的规定,Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的,这点就像我们用磁盘空间去存储文件一样,并不要求每个文件都连续存放。

但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间。

Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定 :后面会发jvm内存参数的文章,不慌不慌)。

如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

5. 方法区

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

虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区分开来。

根据《Java虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。

6. 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分

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

既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

7. 直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。

但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。

显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器寻址空间的限制,一般服务器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,但经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。

总结问题

当然就上面那些肯定是不够的,下面来总结总结里面的细节问题!

那些区域会发生内存溢出?

  • 不会出现内存溢出的区域 – 程序计数器
  • 出现 OutOfMemoryError 的情况
    • 堆内存耗尽 – 对象越来越多,又一直在使用,不能被垃圾回收
    • 方法区内存耗尽 – 加载的类越来越多,很多框架都会在运行期间动态产生新的类
    • 虚拟机栈(本地方法栈)累积 – 每个线程最多会占用 1 M 内存,线程个数越来越多,而又长时间运行不销毁时
  • 出现 StackOverflowError 的区域
    • JVM 虚拟机栈,如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflflowError异常,原因例如:有方法递归调用未正确结束、反序列化 json 时循环引用

类存放在哪里?

放在方法区。

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区分开来

局部变量存放在哪里?

放在Java虚拟机栈的栈帧中。

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,它的生命周期与线程相同。

虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和 returnAddress 类型(指向了一条字节码指令的地址)。


http://chatgpt.dhexx.cn/article/8eUJX5lB.shtml

相关文章

JVM8基础结构图理解

目录 1 理解DOS里面的java命令 2 JVM内存 2.1 JVM主要组成部分 2.2 JVM内存(运行时数据区域) 2.2.1 虚拟机内存与本地内存区别 2.2.2 JVM内存(运行时数据区域)中的JVM内存 2.2.3 程序计数器(Program Counter Register) 2.2.4 虚拟机栈(JVM Stacks) 2.2.5 本地方法栈(N…

java绘制(可视化)树结构图

以JPanel组件为画板,继承JPanel类并重写paint(Graphics g)函数,在函数中使用画笔g绘制树结构图。 实例代码——3个java源文件:Main.java、DrawNode.java、DrawTree.java 1、Main.java package drawTree;public class Main {public static …

Java的理论知识以及结构图

Java基础 1、 简述Java的基本历史 java起源于SUN公司的一个GREEN的项目,其原先目的是:为家用消费电子产品发送一个信息的分布式代码系统,通过发送信息控制电视机、冰箱等 2、 简单写出Java特点,写出5个以上,越多越好…

Swagger使用教程及Swagger增强工具knife4j

标题 课外知识须知什么是swagger什么是RESTful 面向资源URI和URL区别: 博址推荐SpringBoot集成SwaggerSwagger常用注解Swagger增强工具knife4j 重点掌握:编写swagger的配置文件,理解每个配置的作用 课外知识须知 Swagger官网: …

JVM快速入门(类加载,对象创建,运行数据区,GC垃圾回收算法,jvm调优)

JVM快速入门 JVM定义:常见的几种jvmJDK,JRE,JVM区别 类加载过程类加载器作用加载器分类双亲委派机制好处 全盘委托机制 对象的创建流程类加载校验分配内存设置初值设置对象头对象头中的Mark Word 字段(32位)对象头中的…

一起学JVM(GC可视化工具Visual GC)

导读 众所周知,JVM(java虚拟机)运行着我们的java程序。java本身提供了自带工具VisualVM来帮助我们查看JVM的运行情况,下面主要介绍GC的可视化插件-Visual GC java版本 1.8.0_281 工具 VisualVM 的 Visual GC 插…

JVM G1详解

java程序性能 当我们调优java程序时,通常的目标有两个: 响应能力 或者 吞吐量 响应能力 响应能力指一个程序或者系统对请求的是否能够及时响应。 比如: 一个桌面UI能多快的响应一个事件; 一个网站能够多快返回一个页面请求&…

最简单的JVM内存结构图

目录 JVM内存结构图 方法区 堆 栈 程序计数器 本地方法栈 直接内存 内存分配性能优化-逃逸分析 总结 JVM内存结构图 大家好,好几天没有更新了,今天的内容有点多,我们详细介绍下JVM内部结构图,还是和之前一样,案…

JVM进阶(十一):JAVA G1收集器

文章目录 一、前言 一、前言 G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一。早在JDK7就已加入JVM的收集器大家庭中,成为HotSpot重点发展的垃圾回收技术。同优秀的CMS垃圾回收器一样,G1也是关注最小时延的垃圾回收器,也同…

JVM监控之图形化工具

一、工具概述 使用命令行工具存在以下的局限性: 无法获取方法级别的分析数据,如方法之间的调用关系、各方法的调用次数和调用时间等要去用户登陆到java应用所在的宿主机上分析数据通过终端输出,结构不够直观 随着java应用的官方使用&#…

Java 知识结构图

简介 《 值得去的地方,没有捷径;难走的路才更值得开始 》 为什么要说这个【知识结构图】呢,其实是针对于刚开始学习,工作或工作一段时间的人,每天忙,杂七杂八,自己身心巨累,又想要偷…

Java程序员必备基础结构图

前言 最近看了深入理解Java虚拟机第三版,整理了一些基础结构图,算是比较全的了,做一下笔记,大家一起学习。 1.Java虚拟机运行时数据区图 JVM内存结构是Java程序员必须掌握的基础。 程序计数器 程序计数器,可以看作…

JVM 结构图

一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代” 、“非堆”, 它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。默认最小值为16MB,最大值为64MB,可以通过-XX:Per…

JVM结构、GC工作机制详解

题外话:最近在应聘阿里2015暑期实习,感触颇多。机会总是留给有准备的人的,所以平常一定要注意知识的巩固和积累。知识的深度也要有一定的理解,不比别人知道的多,公司干嘛选你?关于JVM和GC,我相信…

详解 JVM Garbage First(G1) 垃圾收集器

前言 Garbage First(G1)是垃圾收集领域的最新成果,同时也是HotSpot在JVM上力推的垃圾收集器,并赋予取代CMS的使命。如果使用Java 8/9,那么有很大可能希望对G1收集器进行评估。本文详细首先对JVM其他的垃圾收集器进行总结,并与G1进行了简单的对比;然后通过G1的内存模型、G1…

【JVM】JVM内存结构之——G1收集器

目录 1. 什么是G12. G1收集器发展历程3. G1收集器分区划分3.1 为什么G1收集器需要设计巨型对象3.2 G1收集器参数设置 3.3 G1收集器回收的细节3.4 G1收集器Rset问题(记忆集)3.5 G1两种回收策略4. G1收集器优缺点 5. G1收集器核心配置参数 1. 什么是G1 G1…

Visual Studio C# WinForm开发入门(5):TabControl 控件使用

TabContrl选项卡控件可创建标签化窗口,在实际 编程中经常用到,该控件的作用是将相关的组件组合到一系列选项卡页面上。 比如下面的例子,在tabPage1页面和tabPage2页面各放了2个checkBox控件,通过点击不同page即可切换:…

WPF自定义TabControl样式

WPF自定义TabControl样式 原文: WPF自定义TabControl样式 WPF自定义TabControl&#xff0c;TabControl美化 XAML代码&#xff1a; <TabControl x:Class"SunCreate.Common.Controls.TabControlEx"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/present…

C# WinForm TabControl美化

窗口Load加下面代码&#xff0c;ItemSize根据自己显示文本长度调整 #region tabMainItem属性设置this.tabModuleMainItem.DrawMode TabDrawMode.OwnerDrawFixed;this.tabModuleMainItem.Alignment TabAlignment.Top;this.tabModuleMainItem.SizeMode TabSizeMode.Fixed;this…

浅谈C#tabcontrol应用

作为Winfrom开发者来说&#xff0c;我们很多时候会用到tabcontrol来实现和网页标签页相关的效果。同时微软自带的控件样式不符合我们的需求&#xff0c;我们该如何去实现更加美观且可以自定义的组合控件呢&#xff1f;带着这个问题进入我们今天的主题&#xff0c;组合控件tabco…