当前位置: 首页 > ai >正文

JVM 内存、JMM内存与集群机器节点内存的联系

目录

1、JVM 内存

1.1、分配机制

1.2、jvm模型位置

1.3、字节码内存块

2、JMM内存

 2.1、JMM模型

2.2、工作流程图

1、工作内存与主内存的交互

2. 多线程下的主内存与堆内存交互

2.3、 主内存与工作内存的同步方案

1、volatile

2、synchronized

3、final

3、内存使用

3.1、集群环境

3.2、容器化环境

3.3、示例场景

4、常见问题与解决方案


前言

        在日常开发过程中,不知道你是否考虑JVM内存模型、JMM模型、计算机内存模型它们是通过何种机制来进行联系的。

问题:

        比如当选择一台物理机器节点部署应用的时候,如何选择规模适度的内存大小?选择了计算机内存后,如何为程序应用选择JVM内存大小。

        在jvm内存大小设定好,JMM模型是如何用jvm交互的呢?本篇将介绍下,它们的联系及内存分配。


1、JVM 内存

1.1、分配机制

        JVM运行内存分配时,其最终由 操作系统 管理。

        JVM 的内存参数(如 -Xms、-Xmx)只是告诉 JVM 它期望使用的内存范围,但实际使用的内存是 运行 JVM 的物理机器或虚拟机节点的内存

如下图所示:

根据上面可以看出:

        JVM内存模型是模仿操作系统内存模型构建的,JVM内存模型和操作系统内存模型是可以一一对应起来的。

1.2、jvm模型位置

关于jvm内存模型,可参考:关于对JVM的知识整理_jvm知识-CSDN博客

         JVM就类似于一个操作系统,整个JVM内存模型存储在操作系统的堆中。

如下图所示:

1、方法区:

        而JVM的方法区,也就相当于操作系统/主机的硬盘区,也叫永久区;而操作系统栈(本地方法栈)和JVM的栈也是一致的;

2、堆:

        JVM堆和操作系统堆在概念上和目标上是一致的,分配内存的方式也是一致的,但JVM堆管理垃圾的方式是GC回收,而操作系统堆则是需要程序员手动释放;

3、PC寄存器:

        计算机上的PC寄存器是计算机上的硬件(是CPU内部用来存放“伪指令”或地址数据的一些小型存储区域)。

        而对于虚拟机,PC寄存器它表现为一块内存(一个字长,虚拟机要求字长最小为32位),存放的是将要执行指令的地址。

1.3、字节码内存块

关于更多方法区的介绍,可参考:关于对JVM的知识整理_jvm知识-CSDN博客

        ClassLoader这个类加载器存放在堆内存,当一个classLoder启动的时候,它会去主机硬盘上将A.class加载到jvm的方法区。

如下所示:

        方法区里面的字节文件会被虚拟机读取并执行new A字节码(),然后在堆内存生成了一个A字节码的对象。

此时方法区里面A字节码内存文件有两个引用:

一个指向A的class对象,一个指向加载自己的classLoader引用。

如下图。

        ⚠️注意:图里面的字段信息应该是字段的结构信息(方法区),而不是字段的值(堆内存)。

对比字节码内存块和类的信息。

代码示例:

public final class ClassStruct extends Object implements Serializable {// 实例变量的值(存储在堆内存)// 实例变量的(结构信息存放在方法区)private String name;private int id;// 静态常量(存储在方法区)public final int CONST_INT = 0;public final String CONST_STR = "CONST_STR";// 静态变量(存储在方法区)public static String static_str = "static_str";// 静态方法(字节码存储在方法区)public static final String getStatic_str() throws Exception {return ClassStruct.static_str;}
}

结论:jvm方法区里面的字节码内存就是将完整的类信息加载到了内存。

参考类的信息:

ClassStruct
├── 类名: "ClassStruct"
├── 父类: "java.lang.Object"
├── 接口: "java.io.Serializable"
├── 字段:
│   ├── name: private java.lang.String
│   ├── id: private int
│   ├── static_str: public static java.lang.String
│   ├── CONST_INT: public final int
│   └── CONST_STR: public final java.lang.String
├── 方法:
│   └── getStatic_str(): public static final
└── 常量池:├── "static_str"├── "CONST_STR"└── 0

       1.类信息:修饰符(public final)

                        是类还是接口(class,interface)

                        类的全限定名(Test/ClassStruct.class)

                        直接父类的全限定名(java/lang/Object.class)

                        直接父接口的权限定名数组(java/io/Serializable)

 public final class ClassStruct extends Object implements Serializable这段描述的信息提取

       2.字段结构信息:修饰符(pirvate)

                            字段类型(java/lang/String.class)

                            字段名(name)

        private String name;这段描述信息的提取。(实例变量的值存放在堆内存里面)

       3.方法信息:修饰符(public static final)

                          方法返回值(java/lang/String.class)

                          方法名(getStatic_str)

                          参数需要用到的局部变量的大小还有操作数栈大小(操作数栈)

                          方法体的字节码(就是花括号里的内容)

                          异常表(throws Exception)

       public static final String getStatic_str ()throws Exception的字节码的提取

         4.常量池:

                   整型直接常量池public final int CONST_INT=0;

                   字符串直接常量池   public final String CONST_STR="CONST_STR";

                    浮点型直接常量池                              

        方法名、方法描述符、类名、字段名,字段描述符的符号引用

      5.类变量:

                  就是静态字段( public static String static_str="static_str";)

                  虚拟机在使用某个类之前,必须在方法区为这些类变量分配空间。

      6.一个到堆内存classLoader的引用:

        通过this.getClass().getClassLoader()。

      7.一个到堆内存class A对象的引用:

        这个对象存储了所有这个字节码内存块的相关信息。

可使用反射:java的反射详解_java中的反射-CSDN博客

1、类信息,你可以通过this.getClass().getName()取得;

2、所有的方法信息,可以通过this.getClass().getDeclaredMethods()。

3、字段信息可以通过this.getClass().getDeclaredFields()。


2、JMM内存

 2.1、JMM模型

        Java 内存模型(Java Memory Model, JMM)定义了多线程环境下变量的可见性、原子性和有序性规则。

如下图所示:

Java 内存模型(JMM)
├── 主内存(所有线程共享)
│   ├── 堆内存(对象实例、数组)
│   ├── 方法区(类元数据信息、常量池、静态变量、字节码文件)
│   └── 静态变量
└── 工作内存(每个线程私有)├── 虚拟机栈(局部变量)└── 本地方法栈(Native 方法使用)

1、堆内存与JMM 主内存的联系

JVM 堆内存是 Java 内存模型(JMM)中主内存的一部分。

堆内存中的对象属于主内存

        所有在堆中创建的对象(如 new object())存储在 JMM 的主内存中。多线程共享这些对象,线程通过工作内存(如 CPU 缓存)读写堆内存中的对象。

JMM 的主内存范围更广

主内存 不仅包含堆内存,还包括:

        方法区(存储类信息、静态方法和字段、常量池、字节码文件等,JDK 8 后移至元空间 Metaspace)。

        静态变量(存储在方法区)。

        局部变量(存储在虚拟机栈中,但变量引用的对象存储在堆中)。

主内存 vs 工作内存

        主内存:所有线程共享的物理内存(包括堆、方法区等)。

        工作内存:每个线程私有的本地内存(如 CPU 寄存器、高速缓存),用于临时存储变量副本。

注意:堆内存 是主内存中用于存储对象实例的核心部分。

总结

2.2、工作流程图

1、工作内存与主内存的交互

2. 多线程下的主内存与堆内存交互

1、变量读写流程

线程访问变量

        如果变量在主内存(如堆中的对象字段),线程会将变量复制到工作内存。修改后,线程将更新后的值写回主内存(通过 happens-before规则保证可见性)。

2、内存可见性问题

  • 默认情况下,线程对变量的修改对其他线程不可见(因为工作内存和主内存的同步延迟)。
  • 解决方案
    • 使用 volatile 关键字:强制每次读写都直接访问主内存。
    • 使用 synchronized 或 Lock:通过锁机制确保内存同步。

代码示例:

public class JMMExample {private int sharedVariable = 0; // 存储在堆内存(主内存)public void increment() {int localVariable = this.sharedVariable; // 从主内存读取到工作内存localVariable++;this.sharedVariable = localVariable; // 将修改后的值写回主内存}
}
  • 线程 A 执行 increment():
    1. 从主内存(堆)读取 sharedVariable 到工作内存。
    2. 修改后写回主内存。
  • 线程 B 读取 sharedVariable:
    • 如果未使用 volatile 或 synchronized,可能读取到旧值(工作内存未同步)。

2.3、 主内存与工作内存的同步方案

1、volatile

关于volatile的实现详细可参考:对于Synchronized和Volatile的深入理解_java sychronized和volatile以及同步代码块-CSDN博客

private volatile int sharedVariable = 0;
  • 作用:禁止线程缓存变量副本,每次读写直接操作主内存。

2、synchronized

关于synchronized的实现可参考:对于Synchronized和Volatile的深入理解_java sychronized和volatile以及同步代码块-CSDN博客

public synchronized void increment() {sharedVariable++;
}
  • 作用:通过锁确保变量的修改对其他线程可见。

3、final

关于final的具体应用,可参考:对于final、finally和finalize不一样的理解-CSDN博客

private final int sharedVariable = 0;
  • 作用:final 变量在构造函数结束后对其他线程可见。


3、内存使用

3.1、集群环境

        在 集群部署(如 Kubernetes、Docker Swarm、物理服务器集群)中,每个 JVM 实例运行在某个 节点(Node) 上。

JVM 的内存分配规则如下:

1、节点物理内存 vs JVM 堆内存

1.JVM 堆内存(Heap Memory)

        通过 -Xms(初始堆大小)和-Xmx(最大堆大小)参数配置。这些参数控制的是 JVM 堆内存,但它会占用 所在节点的物理内存

        例如:-Xmx4g 表示 JVM 最多可以使用 4GB 节点内存用于堆。

2.非堆内存(Metaspace、Direct Memory 等)

        Metaspace(类元数据):默认无上限(需通过 -XX:MaxMetaspaceSize 限制)。

        直接内存(Direct Memory):通过 -XX:MaxDirectMemorySize 配置,同样占用节点物理内存。

如下图所示:

3.2、容器化环境

如Docker/Kubernetes。

1、容器内存限制

        如果 JVM 运行在容器中(如 Docker 容器),容器的内存限制(如 --memory=4g 或 Kubernetes 的 resources.limits.memory)会 限制 JVM 可使用的总内存

        JVM 无法突破容器内存限制,否则会被操作系统强制终止(OOMKilled)。

2、JVM 参数与容器限制的关系

        -Xmx 应小于容器内存限制的 70%-80%,为其他组件(如非堆内存、OS 缓存)预留空间。例如:容器内存限制为 4GB,则 -Xmx 建议设为 3g

3.3、示例场景

1:物理服务器集群

  • 节点配置:8GB 内存。
  • JVM 配置:-Xmx4g
  • 实际内存使用:JVM 最多占用 4GB 节点内存(堆内存),其余内存可用于其他服务(如数据库、中间件)。

2:Kubernetes 集群

  • Pod 配置:resources.limits.memory=4Gi。
  • JVM 配置:-Xmx3g。
  • 实际内存使用:JVM 最多占用 3GB 容器内存,剩余 1GB 用于非堆内存和容器内其他进程。


4、常见问题与解决方案

1、JVM 报 OOM 但节点内存充足

  • 原因
    • JVM 堆内存未配置,导致堆内存无限制。
    • 非堆内存(如 Metaspace)未限制,导致内存泄漏。
  • 解决方案
    • 显式设置 -Xmx 和 -XX:MaxMetaspaceSize。
    • 监控 JVM 内存使用(如通过 Prometheus + Grafana)。

2、容器被 OOMKilled

  • 原因
    • JVM 堆内存 + 非堆内存 + 容器其他进程内存总和超过容器限制。
  • 解决方案
    • 降低 -Xmx,为非堆内存和系统开销预留空间。
    • 使用容器内存配额(如 Kubernetes 的 resources.limits.memory)。


参考文章:

1、JVM内存是对应到操作系统内存_jvm内存和电脑内存的关系-CSDN博客

2、关于对JVM的知识整理_jvm知识-CSDN博客

http://www.xdnf.cn/news/13934.html

相关文章:

  • 【redis——缓存穿透】
  • 基于PSO粒子群优化的VMD-LSTM时间序列预测算法matlab仿真
  • git 下载安装并连接gitee
  • 一键给你的网页增加 ios26 液态玻璃效果
  • Android 手机如何实现本地视频音频提取?实战教程来了
  • 提示词Prompts(2)
  • 提示词Prompts(1)
  • iOS-SM3加密算法N种集成
  • MySql基础教程:事务基础知识
  • 通信安全员A,B,C证有什么区别?
  • 一文讲清网络变压器、芯片和 RJ45 之间的接线
  • WebView工作原理全解析:如何实现混合开发的无缝衔接
  • python transformers库笔记(BertTokenizerFast类)
  • 高频面试之12 HBase
  • javascript中浏览器自带的实用方法
  • 液氮罐里的重要样本老是担心安全受到损坏如何操作可以在线记录开门时间呢?
  • 使用GpuGeek训练图像分类器:从入门到精通
  • ubuntu24.04.2安装docker自动化脚本
  • React Native 性能优化实践
  • 【Linux网络编程】基于udp套接字实现的网络通信
  • 2024年06月青少年软件编程(图形化)等级考试试卷(四级)
  • 一名高级运维工程师,一台新服务器,安装windows系统后,在网络攻防(护网行动)形式下,应该怎么做安全加固?
  • Arduino入门教程:​​​​​​​2、代码基础
  • 在 cuda 基础环境中安装完整的cupy
  • Spring AI Chat Memory 指南
  • Prompt从入门到抄作业
  • 联邦算法分析:技术深度探索与实践应用
  • Linux系统权限提升篇Vulnhub辅助项目SUID权限SUDO指令版本漏洞
  • React ajax中的跨域以及代理服务器
  • python 爬虫,爬取某乎某个用户的全部内容 + 写个阅读 app,慢慢读。