Java 栈与堆:内存分配实战指南
什么是栈内存?
在 Java 中,栈是一块连续内存区域,存放方法调用帧、局部变量和引用变量。每次方法调用,新帧压入栈顶。栈内存遵循后进先出(LIFO)原则,最近的调用最先在方法返回时弹出。
什么是堆内存?
堆是创建 Java 对象的区域。它保存所有类实例,以及可选的指向这些对象的引用变量。与栈不同,堆不绑定单个方法调用的生命周期,允许对象在创建它们的作用域之外持久存在。
Java 内存分配
Java 内存分配 指为程序数据预留虚拟内存的过程。变量声明时不分配内存,而是创建引用。使用 new 操作符时,JVM 在堆上为对象分配空间。
Java 将内存划分为四个不同区域以便高效管理:
- 堆
- 栈
- 代码(字节码)
- 静态(类数据)
- 代码区 存放编译后的字节码。
- 栈 存放方法帧、局部变量和引用变量。
- 堆 存放对象,偶尔也存放引用变量。
- 静态区 存放静态字段和方法。
局部变量 vs 实例变量
实例变量 声明在类中但方法外:
class Student {
int num; // 实例变量
public void showData() {}
}
局部变量 声明在方法内(或作为方法参数):
public void sum(int a) {
int x = a + 3; // a 和 x 是局部变量
}
栈与堆的交互演示
通过示例说明方法调用时栈和堆如何交互。
假设 main 调用 m1():
public void m1() {
int x = 20;
}
m1 的帧压入栈,局部变量 x 存放在该帧中。
m1 调用 m2(int b),新帧压在 m1 帧之上:
public void m2(int b) {
boolean c;
}
m2 调用 m3(),再创建一帧:
m3 内实例化 Account 对象于堆,引用 ref 在栈上:
class Account {
int p;
int q;
}
public void m3() {
Account ref = new Account(); // 额外代码
}
引用变量 ref 在 m3 的栈帧中创建,指向堆上分配的 Account 对象。
m3 完成时,其栈帧弹出,Account 对象无引用,符合垃圾回收条件。
m2 和 m1 同理,最终返回 main。
作为实例变量的对象引用
考虑类 A 持有 B 实例的引用:
public static void main(String[] args) {
A parent = new A();
}
class A {
B child = new B();
int e;
}
class B {
int c;
int d;
}
这里 child 引用作为 A 对象的一部分存放在堆上,指向其自身的 B 实例。
核心要点
- 每次方法调用在栈上创建新帧。
- 局部变量驻留栈上;实例变量在堆上。
- 引用变量存栈但指向堆对象。
- 栈帧弹出时,其局部和引用变量被丢弃,孤儿堆对象符合 GC 条件。
java
- Java 变量与字面量:全面指南
- Java 类与对象:实用指南
- Java 抽象类与抽象方法完全指南
- 精通 Java 嵌套类与内部类:类型、示例与最佳实践
- Java throw 与 throws 深度解析:异常处理实战指南
- Java 注解类型完全指南:预定义、自定义与元注解
- 掌握 Java 自动装箱与拆箱:实战示例与最佳实践
- Java 字符串替换大全:replace()、replaceAll() 与 replaceFirst() 详解
- 精通 Java:理解对象、类与核心 OOP 概念
- Java 方法重写详解:规则、多态调用与 super 协作
- Java 10 堆分配增强:支持 NV-DIMM 等替代内存设备
- 精通 Java 8 方法引用::: 语法与四大引用类型