【Java 开发日记】我们来说一说 JVM 的内存模型

【Java 开发日记】我们来说一说 JVM 的内存模型

目录

前言

JVM 内存结构(运行时数据区)

1. 程序计数器

2. Java 虚拟机栈

3. Java 堆

4. 方法区

5. 运行时常量池

直接内存

总结与对比


前言

JVM 内存结构(JVM Memory Structure) 和 Java 内存模型(Java Memory Model, JMM) 是两个不同的概念,但经常被混淆。

  1. JVM 内存结构:指的是 JVM 在运行时,其内部的数据存储区域是如何划分的(如堆、栈、方法区等)。这是我们接下来要讲解的重点。
  2. Java 内存模型:是一个概念和规范,它定义了多线程环境下,线程如何通过内存进行交互,以及线程如何看到共享变量的值。它解决的是并发编程中的可见性、原子性、有序性问题。

JVM 内存结构(运行时数据区)

JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域各有用途,创建和销毁的时间也不同。根据《Java虚拟机规范》,JVM 内存结构(运行时数据区)如下图所示:

下面我们逐一详细讲解每个部分。

1. 程序计数器
  • 定义:一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器
  • 作用
    • 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制(如顺序执行、循环、跳转、异常处理等)。
    • 在多线程环境下,当一个线程的时间片用完,CPU 会切换到另一个线程。为了之后能切回来并正确恢复到上次执行的位置,每个线程都需要一个独立的程序计数器。
  • 特点
    • 线程私有:每个线程都有自己的程序计数器。
    • 此区域是 唯一一个 在《Java虚拟机规范》中没有规定任何 OutOfMemoryError 情况的区域。
2. Java 虚拟机栈
  • 定义:描述的是 Java 方法执行的内存模型。每个方法在执行的同时都会创建一个栈帧
  • 作用:用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
  • 栈帧:每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
    • 局部变量表:存放了编译期可知的各种基本数据类型(booleanbytecharshortintfloatlongdouble)、对象引用(reference 类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和 returnAddress 类型(指向了一条字节码指令的地址)。
    • 操作数栈:方法执行过程中,用于计算的工作区(类似于CPU的寄存器)。
    • 动态链接:指向运行时常量池中该栈帧所属方法的引用。
    • 方法出口:记录方法返回时,需要返回到哪个位置继续执行。
  • 特点
    • 线程私有,生命周期与线程相同。
    • 会抛出两种错误:
      • StackOverflowError:如果线程请求的栈深度大于虚拟机所允许的深度(例如无限递归)。
      • OutOfMemoryError:如果虚拟机栈可以动态扩展,但在扩展时无法申请到足够的内存。
补充:本地方法栈
它与 Java 虚拟机栈的作用非常相似。区别在于:Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中,本地方法栈和 Java 虚拟机栈合二为一。
3. Java 堆
  • 定义:这是 JVM 所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。
  • 作用此内存区域的唯一目的就是存放对象实例。 几乎所有的对象实例以及数组都在这里分配内存。
  • 特点
    • 线程共享,因此是垃圾收集器管理的主要区域,因此很多时候也被称作 “GC堆”
    • 从内存回收的角度看,由于现代垃圾收集器大部分都基于分代收集理论,所以 Java 堆中经常被细分为:
      • 新生代:新创建的对象首先在这里分配。它又分为:
        • Eden 区
        • Survivor 0 区(From Survivor)
        • Survivor 1 区(To Survivor)
      • 老年代:在新生代中经历多次垃圾回收后仍然存活的对象,会被移动到老年代。
      • 永久代/元空间:注意,这是方法区的实现,逻辑上属于堆的一部分,但物理上可以不连续。
    • 会抛出 OutOfMemoryError:如果在堆中没有内存完成实例分配,并且堆也无法再扩展时。
4. 方法区
  • 定义:用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
  • 作用:可以看作是“类”的仓库。
  • 特点
    • 线程共享
    • 它有一个非常别名的名字——“非堆”,目的是与 Java 堆区分开来。
    • 会抛出 OutOfMemoryError:当方法区无法满足内存分配需求时。

重要演进:方法区的实现

  • 永久代:在 JDK 7 及之前,HotSpot 虚拟机使用 永久代 来实现方法区。这个区域在逻辑上属于堆的一部分,但有其特殊的垃圾回收机制。
  • 元空间:从 JDK 8 开始,HotSpot 虚拟机彻底移除了永久代,改用 元空间 来实现方法区。元空间不再使用 JVM 内存,而是使用本地内存。这带来的好处是:
    • 避免了永久代的内存溢出问题。
    • 类元信息的生命周期与类加载器一致,简化了垃圾回收。
    • 可以动态调整大小,减少了调优的复杂性。
5. 运行时常量池
  • 定义:它是方法区的一部分,用于存放编译期生成的各种字面量符号引用
  • 内容
    • 字面量:如文本字符串、被声明为 final 的常量值等。
    • 符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
  • 特点
    • 具备动态性,并非只有编译期才能产生常量,运行期间也可以将新的常量放入池中,例如 String 类的 intern() 方法。

直接内存

虽然不是 JVM 运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域,但它经常被使用,并且也可能导致 OutOfMemoryError

  • 定义:直接内存并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。它是由 java.nio 包引入的,通过 Native 函数库直接分配的堆外内存。
  • 作用:在 NIO 中,引入了 Channel 与 Buffer 的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为它避免了在 Java 堆和 Native 堆中来回复制数据。
  • 特点
    • 其大小不受 Java 堆大小的限制,但会受到本机总内存大小及处理器寻址空间的限制。
    • 同样会抛出 OutOfMemoryError

总结与对比

内存区域线程共享?存储内容是否可能 OOM / SOF
程序计数器私有当前线程执行的字节码行号不会
Java 虚拟机栈私有栈帧(局部变量表、操作数栈等)SOF 和 OOM
本地方法栈私有Native 方法服务SOF 和 OOM
Java 堆共享对象实例、数组OOM
方法区共享类信息、常量、静态变量、JIT代码OOM
运行时常量池共享字面量、符号引用(是方法区的一部分)OOM
直接内存N/ANIO 使用的堆外缓冲区OOM

理解 JVM 内存模型是理解 Java 程序运行机制、进行性能调优(尤其是 GC 调优)和故障诊断(如内存溢出)的基础。

如果小假的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!

Read more

Java内功修炼(2)——线程安全三剑客:synchronized、volatile与wait/notify

Java内功修炼(2)——线程安全三剑客:synchronized、volatile与wait/notify

1.线程安全 1.1 概念&示例 概念:指在多线程环境下,某个代码、函数或对象能够被多个线程同时调用或访问时,仍能保持正确的行为和数据一致性。简单来说,线程安全的代码在多线程环境下运行可靠,不会因线程间的交互而产生不可预测的结果 示例: publicclassThreadDemo{publicstaticint count =0;publicstaticvoidmain(String[] args)throwsInterruptedException{Thread thread1 =newThread(()->{for(int i =0; i <500000; i++){ count++;}});Thread thread2 =newThread(()->{for(int i =0; i <500000;

By Ne0inhk
Java 中间件:Redis 分布式限流器(Redisson RateLimiter)

Java 中间件:Redis 分布式限流器(Redisson RateLimiter)

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Java中间件这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * Java 中间件:Redis 分布式限流器(Redisson RateLimiter) 🚦 * 什么是限流?为什么需要分布式限流? 🤔 * 限流的定义 * 单机限流 vs 分布式限流 * Redisson 简介:不只是 Redis 客户端 🧰 * Redisson RateLimiter 核心原理 🔍 * Redis 内部数据结构 * 限流流程图(Mermaid) * 快速上手:第一个 Redisson RateLimiter 示例 🚀 * 1. 添加依赖 * 2. 配置 RedissonClient * 3. 创建并使用 RateLimiter * 输出示例:

By Ne0inhk
Java 程序员的 AI 进阶:用 Deeplearning4j 打造工业级推荐引擎

Java 程序员的 AI 进阶:用 Deeplearning4j 打造工业级推荐引擎

文章目录 * 🎯 Java 程序员的 AI 进阶:用 Deeplearning4j 打造工业级推荐引擎 * 📊 1. 为什么 Java 依然是 AI 工程化的“定海神针”? * 🧬 1.1 内存管理的“物理隔离” * 🛡️ 1.2 工业级生态的无缝闭环 * 🌍 2. 数据预处理:AI 模型的“洗经伐髓” * 🧬 2.1 特征工程的“物理建模” * 📊 推荐数据流转对比表: * 🔄 3. ND4J 内核:压榨 JVM 的每一分算力 * 🧬 3.1 堆外内存与 BLAS 加速 * 🏗️ 4. 代码实战:用 DL4J 构建神经网络推荐器 (Neural CF)

By Ne0inhk
JSP技术入门指南【一】利用IDEA从零开始搭建你的第一个JSP系统

JSP技术入门指南【一】利用IDEA从零开始搭建你的第一个JSP系统

Jsp技术入门指南【一】利用IDEA从零开始搭建你的第一个JSP系统 * 前言 * 一、什么是JSP * 1.1 JSP是干什么的? * 1.2 JSP与Servlet的关系是什么? * 二、在Idea中创建第一个JSP系统 * 三、JSP和HTML的差别 * 3.1 格式区别 * 3.2 注释区别 前言 * 在前面的内容中,我们已经系统学习了 Web 开发的基础技术:从构建网页骨架的 HTML、美化页面的 CSS,到实现服务器端逻辑的 Java Servlet。 * 这些知识为我们打开了动态 Web 开发的大门,让我们能够通过 Servlet 处理客户端请求、操作数据库并返回动态数据。 * 然而,在 Servlet 中直接拼接 HTML 代码实现页面渲染时,代码往往显得繁琐且难以维护 —— 有没有一种更简洁、更直观的方式,

By Ne0inhk