理解 Java 对象在堆内存中的分配机制
掌握对象引用与实际对象的存储位置区别(堆 vs 栈)
了解 Java 堆的内部结构(新生代、老年代、Metaspace)
熟悉对象的内存布局(对象头、实例数据、对齐填充)
了解主流垃圾回收器的特点与适用场景
在 Java 中,所有通过 new 关键字创建的对象实例都存储在堆内存中,而指向这些对象的引用变量则存储在栈内存中。
Student s = new Student("Alice", 20);new Student(...) 创建的对象 → 存储在 堆(Heap)
引用变量 s → 存储在 栈(Stack),其值为堆中对象的地址
注意:仅声明类类型的变量(如
Student s;)不会创建对象,只会创建一个未初始化的引用(默认为null)。
堆是 JVM 中最大的一块内存区域,用于动态分配所有对象实例和数组。
在 Java 7 及更早版本中,类元数据存储在 永久代(PermGen),位于堆内,容易因类加载过多导致
OutOfMemoryError: PermGen space。Java 8 移除 PermGen,改用 Metaspace,显著提升了类加载的灵活性和稳定性。
堆是垃圾回收器(GC)的主要工作区域。JVM 自动识别并回收不再被引用的对象,释放内存。
线程私有:每个线程拥有独立的栈
自动管理:方法调用时压入栈帧,返回时自动弹出
存储内容:
局部变量(包括基本类型如 int, double)
方法参数
对象引用(注意:不是对象本身!)
public static void main(String[] args) {
int n = 10; // 基本类型 → 栈
Student s = new Student("Bob", 20); // 引用 s → 栈;对象 → 堆
calculate(n); // 参数 n 的副本 → 栈
}
public static int calculate(int n) {
int ans = n * 10; // 局部变量 → 栈
return ans;
}栈内存分配和释放速度极快,但容量有限。深度递归可能导致
StackOverflowError。
每个 Java 对象在堆中由三部分组成:
Object Header
Mark Word:包含哈希码、GC 分代年龄、锁状态(偏向锁、轻量级锁等)、线程 ID 等
Class Pointer:指向对象所属类的元数据(在 Metaspace 中)
Instance Data
对象的实际字段(成员变量)
字段顺序可能被 JVM 重排以优化内存对齐
Padding
保证对象总大小为 8 字节的倍数(HotSpot 要求),提升 CPU 访问效率
开发者可通过
jdk.internal.misc.Unsafe或第三方工具(如 JOL - Java Object Layout)查看对象内存布局。
Garbage Collectors
JVM 提供多种 GC 算法,适用于不同应用场景:
选择 GC 策略需权衡 吞吐量 与 停顿时间。
避免过早优化:优先编写清晰代码,再根据性能分析调整
合理设置堆大小:通过 -Xms / -Xmx 避免频繁扩容
监控 GC 行为:使用 jstat, VisualVM, GC logs 分析停顿与回收效率
减少短生命周期大对象:避免直接进入老年代
谨慎使用 static 集合:易导致内存泄漏(对象无法被回收)
如果一个对象只被局部变量引用,当方法执行完毕后,该对象是否立即被回收?为什么?
在 Java 8 之后,字符串常量池(String Pool)存储在堆还是 Metaspace?为什么这样设计?
对象头中的“锁信息”是如何支持 synchronized 关键字实现的?偏向锁、轻量级锁、重量级锁分别在什么场景下升级?