Java基础语言进阶学习——1,JVM内存模型(堆、栈、方法区)
JVM内存模型(堆、栈、方法区)
学习目标:深入理解JVM内存模型中的堆、栈和方法区
文章目录
目录
JVM内存模型(堆、栈、方法区)
文章目录
一、堆、栈和方法区的比喻:工厂车间
二、JVM Stacks(栈)
1.核心概念
2.栈帧(Stack Frame)结构
局部变量表(Local Variable Table)
操作数栈(Operand Stack)
动态链接(Dynamic Linking)
方法返回地址(Return Address)
栈的异常
三,Heap(堆)
核心概念
1.线程共享:JVM中最大的一块内存区域,所有线程都共享此区域。
2.存储内容:对象实例、数组、字符串常量池(JDK7+)、静态变量(从JDK7起)
3.GC策略:垃圾收集器管理的主要区域,被称为“GC堆”。
4.分区结构:分代结构是堆的核心设计。
5.堆的异常
四,方法区
1.核心概念
1,线程共享:与堆一样,是各个线程共享的内存区域。
2,存储内容:
即时编译代码缓存(JIT Cache):热点方法编译后的机器码
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)
- 存储方法退出后下一条指令的位置
- 方法退出方式:
- 正常退出:return指令(PC计数器记录地址)
- 异常退出:异常表记录处理地址
- 异常处理路径:
- 若方法未处理异常 → 栈帧被弹出并传递给调用方
- 若所有栈帧均未处理 → 线程终止
详细例子分析:
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。
此时栈的状态:
|--------------------|
| main方法的栈帧 | <-- 栈顶
|--------------------|
3,调用add方法:在计算add(a, b)时,add方法的栈帧被压入栈。
局部变量表:存储参数 x (值为10),y (值为20),以及局部变量sum.
此时栈的状态:
|--------------------|
| add方法的栈帧 | <-- 栈顶
|--------------------|
| main方法的栈帧 |
|--------------------|
4,add方法执行完毕:add方法执行到return sum; 其栈帧出栈。返回值被传递给main方法栈帧中的result变量。
此时栈的状态:
|--------------------|
| main方法的栈帧 | <-- 栈顶
|--------------------|
5,main方法执行完毕:main方法栈帧出栈,栈空,线程结束。
栈的异常
StackOverflowError:
如果线程请求的栈深度大于虚拟机允许的深度(比如无限递归),就会抛出此错误。
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、优化高并发应用内存的关键基础!
详细例子:
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对象实例。
最终内存结构图:
栈 (Stack) 堆 (Heap)
|----------------| |-------------------|
| main栈帧 | | |
| ... | | ... |
| person (引用) --------→ | Person对象实例 |
| | | - name (引用) → | "Alice" (String对象) |
|----------------| | - age = 25 | |-------------------|
5.堆的异常
-
OutOfMemoryError:当堆中没有足够的内存完成实例分配,并且堆也无法再扩展时,抛出此错误。这是最常见的内存错误之一。
四,方法区
1.核心概念
1,线程共享:与堆一样,是各个线程共享的内存区域。
2,存储内容:
- 类信息(Class Metadata):
- 类全限定名(如
java.lang.String) - 方法字节码
- 字段定义
- 类全限定名(如
- 运行时常量池(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 |
详细例子:
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.内存结构图:
栈 (Stack) 堆 (Heap) 方法区 (Method Area)
|----------------| |-------------------| |-------------------------|
| main栈帧 | | | | MethodAreaExample类信息 |
| ... | | ... | | - 方法字节码 (main, printMessage) |
|----------------| |-------------------| | - 静态变量 STATIC_FIELD (引用) || "我是一个静态变量" | ←-----| - 常量 CONSTANT_FIELD (引用) → | "我是一个常量" || "我是一个常量" | |-------------------------||-------------------|
方法区的异常
-
OutOfMemoryError:在JDK8之前,如果加载的类过多(比如大量动态生成类),可能导致永久代内存溢出。在元空间中,如果本地内存不足,同样会抛出此错误
五,总结与对比
| 特性 | 虚拟机栈 | Java堆 | 方法区 |
|---|---|---|---|
| 线程共享性 | 线程私有 | 线程共享 | 线程共享 |
| 存储内容 | 栈帧、局部变量、操作数栈 | 对象实例、数组 | 类信息、常量、静态变量、JIT代码 |
| 生命周期 | 与线程相同 | 与JVM进程相同 | 与JVM进程相同 |
| 内存错误 | StackOverflowError OutOfMemoryError | OutOfMemoryError (最常见) | OutOfMemoryError |
| 比喻 | 工人的工作台 | 中央仓库 | 设计图纸库 |
六,综合案例:
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();}
}