【JVM | 第四篇】—— JVM 内存分配

这篇文章会用最直白的语言、最清晰的表格和可运行的代码,帮你彻底搞懂 JVM 三大核心内存区域的分工,以及所有变量类型的内存分配规则。

如果对JVM内存有不了解的同学可以看我这篇【JVM | 第一篇】------ JVM内存区域详解-CSDN博客

一、核心前提:彻底分清 "引用" 与 "实例"

这是所有 JVM 内存问题的基石,90% 的混淆都源于没分清这两个完全不同的东西。

概念 本质 存储位置 作用 类比
引用(Reference) 一个内存地址(64 位 JVM 开启指针压缩后占 4 字节) 栈、堆、元空间 指向堆中的实际对象 电视机的遥控器
实例(Instance) 实际的对象数据(对象头 + 成员变量值 + 对齐填充) 100% 在堆中(没有任何例外) 存储真正的业务数据 电视机本身

✅可以直接记: 所有通过new关键字创建的东西(对象实例 + 数组),永远只存在堆中。栈、元空间里永远只存引用基本类型的值,绝对不存实际对象数据。

你拿着遥控器(引用)可以控制电视机(实例),但遥控器不是电视机,电视机也不会跑到你手里(堆里的对象永远不会跑到栈里)。

二、三类变量的完整内存分配详解

2.1 基础类型变量

8 种基本类型(byte/short/int/long/float/double/char/boolean)没有 "引用" 和 "实例" 的概念,直接存储值本身。它的存储位置完全取决于它是 "局部变量" 还是 "成员变量"

(1)局部基础类型变量(方法里的基础变量)

  • 定义:方法内部定义的基础类型变量
  • 存储位置:虚拟机栈的局部变量表
  • 生命周期:方法执行时创建,方法执行完毕自动释放
java 复制代码
public void test() {
    int a = 10; // 栈中直接存值10,没有引用,没有堆内存
    double b = 95.5; // 栈中直接存值95.5
}

(2)成员基础类型变量(类里的基础变量)

  • 定义:类的非静态成员变量(实例变量)
  • 存储位置:堆中,作为所属对象实例的一部分
  • 生命周期:和所属对象实例一致,对象被 GC 回收时释放
java 复制代码
class User {
    // 当new User()时,age的值会被存到堆中的User对象里
    private int age;
}

2.2 对象实例(所有自定义类、包装类、数组)

创建一个对象分为两个完全独立的步骤,分别使用不同的 JVM 内存区域:

  1. 中分配内存,创建实际的对象实例
  2. 在对应位置创建引用,指向堆中的这个实例

(1)局部对象引用

  • 引用位置:虚拟机栈的局部变量表
  • 实例位置:
  • 内存图解:
java 复制代码
栈(局部变量表)        堆
user=0x1234  ----->  0x1234: User对象{name=0x5678, age=25}
                        0x5678: "张三"字符串对象

(2)成员对象引用

  • 引用位置:堆中,作为所属对象实例的一部分
  • 实例位置:堆(另一个独立的对象)
  • 示例:
java 复制代码
class User {
    // name引用存在堆中的User对象里,指向堆中另一个独立的字符串对象
    private String name;
}

(3)数组(特殊的对象)

数组本身是一个对象,100% 存在堆中。无论数组元素是基础类型还是引用类型,全部都存在堆中。

java 复制代码
public void test() {
    // 栈中创建引用arr,堆中创建int数组对象
    int[] arr = new int[5];
    arr[0] = 10; // 10这个值存在堆中的数组对象里,不是栈中
}

2.3 静态变量

这是面试最高频的考点,也是 90% 的人都会记错的部分,请务必记住:

静态变量的引用存在元空间(方法区) ,但它指向的对象实例 仍然100% 在堆中

完整分配过程

  1. 类加载阶段,在元空间中分配静态变量的引用空间
  2. 类初始化阶段(第一次访问类时),在中创建对应的对象实例
  3. 元空间中的引用指向堆中的实例

内存图解

java 复制代码
元空间(方法区)        堆
staticUser=0xdef0  ----->  0xdef0: User对象{name=0x9abc, age=100}
staticAge=20                0x9abc: "静态用户"字符串对象

关键特性

  • 静态变量的生命周期和JVM 进程完全一致,直到 JVM 退出才会释放
  • 所有线程共享同一个静态变量
  • 这就是为什么静态集合会导致严重的内存泄漏:只要 JVM 不退出,静态集合持有的对象永远不会被 GC 回收

三、综合代码逐行拆解

下面这段代码包含了所有常见的变量类型,我逐行标注了每个部分的存储位置,你可以直接复制运行验证。

java 复制代码
public class MemoryAllocationDemo {
    // 1. 静态基础类型:值直接存在元空间
    private static int STATIC_INT = 100;
    // 2. 静态对象引用:引用存在元空间,指向堆中的String实例
    private static String STATIC_STR = "静态字符串";
    // 3. 静态对象引用:引用存在元空间,指向堆中的User实例
    private static User STATIC_USER = new User("静态用户", 100);

    // 4. 成员基础类型:值存在堆中的MemoryAllocationDemo实例里
    private int memberInt = 200;
    // 5. 成员对象引用:引用存在堆中的MemoryAllocationDemo实例里,指向堆中的String实例
    private String memberStr = "成员字符串";

    public static void main(String[] args) {
        // 6. 局部基础类型:值存在main线程栈的局部变量表
        int localInt = 300;
        // 7. 局部对象引用:引用存在main线程栈,指向堆中的User实例
        User localUser = new User("本地用户", 25);
        // 8. 局部数组引用:引用存在main线程栈,指向堆中的int数组实例
        int[] localArr = new int[5];
        localArr[0] = 10; // 10存在堆中的数组实例里

        // 9. 多个引用指向同一个堆实例
        User anotherUser = localUser; // 栈中创建新引用,指向同一个堆对象
        anotherUser.setAge(30); // 修改堆中的对象,localUser也会看到变化
    }

    static class User {
        private String name; // 引用存在堆中的User实例里,指向堆中的String实例
        private int age; // 值存在堆中的User实例里

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }
}

四、5 个内存误区

  1. ❌ 错误:静态变量存在堆中 ✅ 正确:静态变量的引用 存在元空间,实例存在堆中

  2. ❌ 错误:基本类型数组的元素存在栈中 ✅ 正确:数组本身是对象,所有元素都存在堆中

  3. ❌ 错误:包装类(Integer、Long)存在栈中 ✅ 正确:包装类是对象,实例存在堆中,栈中只存引用(除了 - 128~127 的缓存)

  4. ❌ 错误:字符串字面量存在方法区 ✅ 正确:JDK7 及以上,字符串常量池移到了堆中,所有字符串实例都在堆里

  5. ❌ 错误:对象的方法存在堆中 ✅ 正确:方法的字节码存在元空间,堆中的对象只存成员变量,不存方法

五、总结

  • :存局部变量(基础类型值 + 对象引用),方法执行完自动释放
  • :存所有对象实例和数组,由 GC 负责回收
  • 元空间 :存类元信息、静态变量引用、方法字节码,JVM 退出时释放
相关推荐
Raink老师1 小时前
【AI面试临阵磨枪-89】Skill 幻觉、参数缺失、格式错误、业务异常如何处理?
面试·职场和发展
linweidong2 小时前
Java 后端开发面试 50 个高频易混淆知识点详解
java·spring boot·spring·spring cloud·面试·mybatis·spring事务
weelinking10 小时前
【产品】12_接入数据库——让数据永久保存
jvm·数据库·python·react.js·数据挖掘·前端框架·产品经理
笑尘~Y11 小时前
每日技术面试高频题精选
面试
拼尽全力前进11 小时前
Guava Cache vs Caffeine 面试详解
面试·职场和发展·guava
gsls20080812 小时前
JVM 堆内存参数 & Docker 容器适配,一次讲清楚
jvm·docker·容器
kyriewen13 小时前
大文件上传最全指南:分片、断点续传、秒传,一篇就够了
前端·javascript·面试
我叫黑大帅14 小时前
解决聊天页内部滚轮改为页面滚动问题
javascript·后端·面试
Yeyu16 小时前
Android 渲染流水线全解析:从 Choreographer 到 SurfaceFlinger
面试