一、堆(Heap)与栈(Stack)#
Heap(堆)和 Stack(栈)都在内存中,它们是同一块物理内存(电脑的 RAM)被划分出来的两个不同用途的区域。一个是快速自动回收的小区(栈),另一个是存放大对象、需要 GC 打扫的大区(堆)。
场景:你写了一个方法,叫 买东西():
Java
void 买东西() {
int 钱 = 1000; *// ← 这就是“栈”上的东西*
String 购物车 = new String("苹果"); *// ← 这就是“堆”上的东西*
}当你调用 买东西() 这个方法时:
栈(上下铺)发生了什么?
系统立刻在栈上给你分配一个小空间。
把 钱 = 1000 这个临时变量放进去。
方法开始执行……
方法一结束(买完东西了),栈上的 钱 立刻自动消失,空间马上回收。
特点:快、自动、干净,像住酒店,退房就走。
堆(大小区)发生了什么?
new String(“苹果”) 这句话会在堆上申请一个大房间。
“苹果”这个对象就住在堆里。
即使 买东西() 方法结束了,只要还有其他地方拿着“购物车”这个钥匙(引用),这个“苹果”对象就继续住在堆里。
只有当没有任何人再用它时,垃圾回收器(GC)才会过来把它清理掉。
| 特点 | 栈(Stack) | 堆(Heap) |
|---|---|---|
| 存放什么 | 基本类型变量、对象引用、方法参数 | 真正 new 出来的对象 |
| 内存大小 | 很小(几 MB 到几十 MB) | 很大(几百 MB 到几十 GB) |
| 分配和释放速度 | 非常快 | 较慢 |
| 是否有碎片 | 没有(严格按顺序进出) | 有(会产生内存碎片,靠 GC 整理) |
| 线程安全 | 每个线程都有自己的栈,互不干扰 | 所有线程共享一个堆,需要小心并发 |
| 出错时提示 | 栈溢出(StackOverflowError) | 堆内存不足(OutOfMemoryError) |
| 谁来清理 | 方法结束,系统自动清理 | 垃圾回收器(GC)自动清理 |
总结:
栈:短、快、小、自动 → 放临时数据,像住酒店,住完就走;是用来放“临时小东西”的,速度飞快,自动管理;
堆:长、慢、大、需要 GC → 放真正重要的对象,像买房子,住很久,脏了由保洁阿姨(GC)来打扫;堆是用来放“真正大对象”的,空间大,但需要垃圾回收器帮忙清理。
栈溢出:你写了一个无限递归的方法(方法不停地调用自己),栈空间很快就满了 → 程序直接崩溃(StackOverflow)。
堆内存溢出:你不停地 new 对象,但从来不让它们被回收 → 堆慢慢被撑爆 → OutOfMemoryError。
二、GC垃圾回收–用来处理堆数据#
自动回收程序中不再使用的堆内存空间,防止内存泄漏,让程序能持续稳定运行。安全地 **释放这些对象占用的内存,**把释放出来的内存重新交给程序使用(分配新对象)。
它解决了什么问题?
在 C/C++ 中,程序员必须手动 malloc() 分配内存,手动 free() / delete 释放。
忘记释放 → 内存泄漏(程序越跑内存占用越高,最终崩溃)
提前释放 → 悬挂指针(使用已释放的内存,导致崩溃或安全漏洞)
Java / Go 等语言引入垃圾回收器后,程序员完全不用手动管理内存,把这个繁琐且容易出错的工作交给垃圾回收器自动完成。
根本作用:自动释放不再使用的堆内存。
直接好处:程序员不用操心内存释放,避免内存泄漏和 dangling pointer(悬挂指针)。
实际效果:让程序长时间运行也不会因为内存耗尽而崩溃(当然,仍然可能因为 GC 频繁或Stop-The-World(STW)导致性能问题)。