Java基础语言进阶学习——1,JVM内存模型(堆、栈、方法区)

JVM内存模型(堆、栈、方法区)

学习目标:深入理解JVM内存模型中的堆、栈和方法区


文章目录

目录

JVM内存模型(堆、栈、方法区)

文章目录

一、堆、栈和方法区的比喻:工厂车间

[二、JVM Stacks(栈)](#二、JVM Stacks(栈))

1.核心概念

[2.栈帧(Stack Frame)结构](#2.栈帧(Stack Frame)结构)

[局部变量表(Local Variable Table)](#局部变量表(Local Variable Table))

[操作数栈(Operand Stack)](#操作数栈(Operand Stack))

[动态链接(Dynamic Linking)](#动态链接(Dynamic Linking))

[方法返回地址(Return Address)](#方法返回地址(Return Address))

栈的异常

三,Heap(堆)

核心概念

1.线程共享:JVM中最大的一块内存区域,所有线程都共享此区域。

2.存储内容:对象实例、数组、字符串常量池(JDK7+)、静态变量(从JDK7起)

3.GC策略:垃圾收集器管理的主要区域,被称为"GC堆"。

4.分区结构:分代结构是堆的核心设计。

5.堆的异常

四,方法区

1.核心概念

1,线程共享:与堆一样,是各个线程共享的内存区域。

2,存储内容:

[即时编译代码缓存(JIT Cache):热点方法编译后的机器码](#即时编译代码缓存(JIT Cache):热点方法编译后的机器码)

[3. 历史演变](#3. 历史演变)

4.元空间(Metaspace)进阶详解

5.内存结构图:

五,总结与对比

六,综合案例:


一、堆、栈和方法区的比喻:工厂车间

在开始之前,我们先建立一个宏观印象:

  • :就像一个巨大的中央仓库,存放着所有生产出来的"产品"(对象实例)。大家都可以申请使用这里的空间。

  • :就像每个工人手边的工作台,工作台上放着当前正在组装的零件(局部变量)。每个工人(线程)都有自己的工作台,互不干扰。

  • 方法区 :就像工厂的设计图纸库,存放着所有产品的设计图(类信息、常量、静态变量)。

二、JVM Stacks(栈)

1.核心概念

1.线程私有:每个线程仔创建时都会同步创建一个虚拟机栈。生命周期与线程相同。

2.存储内容:存储栈帧(Stack Frame),每个栈帧对应一个方法的执行。

3.作用:描述Java方法执行的内存模型。每个方法从调用大执行完毕,都对应这一个栈帧在虚拟机栈中从入栈到出栈的过程。

  • 入栈:方法调用时创建一个新的栈帧压入栈顶。
  • 出栈:方法返回时(包括正常return或异常抛出)弹出当前栈帧。

2.栈帧(Stack Frame)结构

局部变量表(Local Variable Table)

  • 存放方法参数和方法内部定义的局部变量
  • 存储数据类型:
    • 基本数据类型(int, boolean等)
    • 引用类型(对象引用指针,如Object
  • 大小固定:编译期确定大小(不会运行时扩大)
  • Slot结构:
    • 每个槽位(Slot)占32位(4字节)
    • long/double需占用2个连续Slot

操作数栈(Operand Stack)

  • 临时工作区(类似CPU寄存器)
  • 实现字节码指令的运算(如iadd加法操作需从栈顶取2个值)
  • 后进先出结构:所有计算基于栈顶元素进行

动态链接(Dynamic Linking)

  • 指向运行时常量池的符号引用(如com/example/MyClass#method()
  • 指向运行时转换为直接引用(方法实际地址)

作用:

  • 支持多态(虚方法绑定)
  • 解决跨类方法调用的引用确定问题

方法返回地址(Return Address)

  • 存储方法退出后下一条指令的位置
  • 方法退出方式:
    1. 正常退出:return指令(PC计数器记录地址)
    2. 异常退出:异常表记录处理地址
  • 异常处理路径:
    1. 若方法未处理异常 → 栈帧被弹出并传递给调用方
    2. 若所有栈帧均未处理 → 线程终止

详细例子分析:

java 复制代码
public clas StackExample{
    public static void main(String[] args){
        int a = 10;
        int b = 20;
        int result = add(a,b);
        System.out.println(result);
    }

    public static int add(int x, int y){
        int sum = x + y;
        return sum;
    }
}

分析栈的变化:

1,程序启动:main线程启动,JVM为其创建虚拟机栈。

2,执行main方法:main方法的栈帧被压入栈。

局部变量表:存储args, a, b, result。

此时栈的状态:

java 复制代码
|--------------------|
| main方法的栈帧      | <-- 栈顶
|--------------------|

3,调用add方法:在计算add(a, b)时,add方法的栈帧被压入栈。

局部变量表:存储参数 x (值为10),y (值为20),以及局部变量sum.

此时栈的状态:

java 复制代码
|--------------------|
| add方法的栈帧       | <-- 栈顶
|--------------------|
| main方法的栈帧      |
|--------------------|

4,add方法执行完毕:add方法执行到return sum; 其栈帧出栈。返回值被传递给main方法栈帧中的result变量。

此时栈的状态:

java 复制代码
|--------------------|
| main方法的栈帧      | <-- 栈顶
|--------------------|

5,main方法执行完毕:main方法栈帧出栈,栈空,线程结束。

栈的异常

StackOverflowError:

如果线程请求的栈深度大于虚拟机允许的深度(比如无限递归),就会抛出此错误。

java 复制代码
public class StackOverflowDemo{
    public static void recursiveMethod(){
        recursiveMethod();//无限递归自己
    }
    public static void main(String[] args){
        recursiveMethod();
    }
}

OutOfMemoryError:

如果虚拟机栈可以动态扩展,但在扩展时无法申请到足够的内存,就会报此错误。

三,Heap(堆)

核心概念

1.线程共享:JVM中最大的一块内存区域,所有线程都共享此区域。

为规避线程安全问题,JVM设计了 TLAB(Thread-Local Allocation Buffer) 机制:

  • 每个线程在Eden区有独立的分配缓冲区
  • 小对象优先在TLAB分配(无需锁)
  • 大对象直接分配在共享堆空间(需同步)

2.存储内容:对象实例、数组、字符串常量池(JDK7+)、静态变量(从JDK7起)

3.GC策略:垃圾收集器管理的主要区域,被称为"GC堆"。

补充机制:

  • 分代收集理论:年轻代(Minor GC)与老年代 (Major GC).
  • GC类型触发条件:
  • Eden满 ------> Minor GC
  • 老年代满 ------> Maior GC
  • Metaspace满/System.gc()------> Full GC

4.分区结构:分代结构是堆的核心设计。

  • 新生代(Eden + Survivor S0/S1)
  • 老年代(对象长期存活区)

关键分区解析

  • 转移规则
    • 对象在Eden分配
    • Minor GC存活 → 移入Survivor区(年龄+1)
    • 年龄达阈值(默认15)→ 晋升老年代

扩展说明:JDK8+永久代被元空间(Metaspace)替代,堆只存储对象数据

解决的问题:掌握分代设计与GC触发机制,是解决OOM、优化高并发应用内存的关键基础!

详细例子:

java 复制代码
public class HeapExample{
    public static void main(String[] args){
        Person person = new Person("Alice", 25);
    }
}

class Person{
    private String name;
    private int age;

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

内存分配过程:

1,Person person :这会在当前线程(main) 的栈帧的局部变量表中创建一个person变量。这个变量是一个引用类型,它现在还没有指向任何对象,值是null.

2,new Person("Alice", 25): new关键字会在堆中开辟一块内存空间,用于存储新创建的Person对象。这个对象包含了其内部数据name和age。

注意:String name 本身也是一个对象。字符串"Alice" 如果不存在,可能会在堆内的字符串常量池(JDK7后移至堆)中创建。

3,= :赋值操作将栈中的person引用指向了堆中的Person对象实例。

最终内存结构图:

java 复制代码
栈 (Stack)                 堆 (Heap)
|----------------|        |-------------------|
| main栈帧        |        |                   |
| ...            |        | ...               |
| person (引用) --------→ | Person对象实例     |
|                |        |   - name (引用) → | "Alice" (String对象) |
|----------------|        |   - age = 25     | 
                          |-------------------|

5.堆的异常

  • OutOfMemoryError:当堆中没有足够的内存完成实例分配,并且堆也无法再扩展时,抛出此错误。这是最常见的内存错误之一。

四,方法区

1.核心概念

1,线程共享:与堆一样,是各个线程共享的内存区域。

2,存储内容:

  1. 类信息 (Class Metadata):
    • 类全限定名(如java.lang.String)
    • 方法字节码
    • 字段定义
  2. 运行时常量池 (Runtime Constant Pool):
    • 符号引用(#12 = Methodref
    • 字面量(String s="hello""hello"引用)

即时编译代码缓存(JIT Cache):热点方法编译后的机器码

3. 历史演变

  • JDK7及之前:永久代(PermGen)作为方法区实现(在堆内)
  • JDK8+ :元空间(Metaspace)替代永久代
    • 存储位置:本地内存(Native Memory)
    • 关键优势:自动扩容(无PermGen OOM

4.元空间(Metaspace)进阶详解

元空间 vs 永久代

特性 永久代(PermGen) 元空间(Metaspace)
存储位置 堆内存内 本地内存(非堆)
内存上限 -XX:MaxPermSize=256m 默认无上限(受OS限制)
OOM风险 高频(类加载过多) 显著降低(仍可能OS耗尽)
垃圾回收触发 Full GC时回收 独立于堆GC

详细例子:

java 复制代码
public class MethodAreaExample{
    //静态变量------>存储在方法区
    pbulic static String STATIC_FIELE = "我是一个静态变量";

    //常量------> 存储在方法区的运行是常量池
    public static final String CONSTANT_FIELD = "我是一个常量";

    public static void main(String[] args){
        //类信息 -> 存储在方法区
        //当第一次使用这个类时,JVM会加载它,并将其他类信息(类目,方法代码,字段信息等)存入方法区中。

        //调用静态方法
        printMessage();
}

    public static void printMessage(){
        //方法的字节码------> 存储在方法区
        System.out.println(STATIC_FIELD);
        System.out println(CONSTANT_FIELD);
    }
}

5.内存结构图:

java 复制代码
栈 (Stack)                 堆 (Heap)                   方法区 (Method Area)
|----------------|        |-------------------|        |-------------------------|
| main栈帧        |        |                   |        | MethodAreaExample类信息 |
| ...            |        | ...               |        | - 方法字节码 (main, printMessage) |
|----------------|        |-------------------|        | - 静态变量 STATIC_FIELD (引用) |
                          | "我是一个静态变量"  | ←-----| - 常量 CONSTANT_FIELD (引用) → | "我是一个常量" |
                          | "我是一个常量"      |        |-------------------------|
                          |-------------------|

方法区的异常

  • OutOfMemoryError:在JDK8之前,如果加载的类过多(比如大量动态生成类),可能导致永久代内存溢出。在元空间中,如果本地内存不足,同样会抛出此错误

五,总结与对比

特性 虚拟机栈 Java堆 方法区
线程共享性 线程私有 线程共享 线程共享
存储内容 栈帧、局部变量、操作数栈 对象实例、数组 类信息 、常量、静态变量、JIT代码
生命周期 与线程相同 与JVM进程相同 与JVM进程相同
内存错误 StackOverflowError OutOfMemoryError OutOfMemoryError (最常见) OutOfMemoryError
比喻 工人的工作台 中央仓库 设计图纸库

六,综合案例:

java 复制代码
public class ComprehensiveDemo {
    // 静态变量 -> 方法区
    private static String staticName = "GlobalName";

    // 实例变量 -> 随对象存在于堆
    private int instanceId;

    public ComprehensiveDemo(int id) {
        this.instanceId = id;
    }

    public void printInfo() {
        // 局部变量 -> 栈
        String localInfo = "Id: " + this.instanceId;
        System.out.println(localInfo);
        System.out.println(staticName);
    }

    public static void main(String[] args) {
        // 引用变量 'demo' 在栈中
        // 对象 new ComprehensiveDemo(1) 在堆中
        ComprehensiveDemo demo = new ComprehensiveDemo(1);

        // 调用方法,创建方法栈帧
        demo.printInfo();
    }
}
相关推荐
毕设源码-郭学长3 小时前
【开题答辩全过程】以 常二社区线上养老院管理系统为例,包含答辩的问题和答案
java·eclipse
yychen_java4 小时前
基于Java3D与Jzy3D的三维建模深度开发:从架构到实践
java·3d·架构
lang201509285 小时前
Maven 入门指南
java·maven
xrkhy5 小时前
Java全栈面试题及答案汇总(2)
java·开发语言
冷崖5 小时前
网络学习-异步IO(八)
服务器·网络·学习
Java爱好狂.5 小时前
接上篇:如何在项目中实现ES查询功能?
java·运维·jenkins·es·java面试·后端开发·java程序员
Iloveskr5 小时前
markdown转为pdf导出
java·pdf
一缕茶香思绪万堵6 小时前
028.爬虫专用浏览器-抓取#shadowRoot(closed)下
java·后端
Deamon Tree6 小时前
如何保证缓存与数据库更新时候的一致性
java·数据库·缓存