Java堆与栈核心区别及多线程场景的处理
一、堆(Heap)与栈(Stack)核心区别对比
为了方便理解,我用表格形式先梳理核心差异:
维度 堆(Heap) 栈(Stack) 存储内容 对象实例、数组、静态变量、常量池 局部变量、方法参数、方法调用栈帧 内存管理 手动/自动(GC)回收,分配复杂 自动压栈/出栈,内存连续且固定大小 线程可见性 全局共享,所有线程可访问 线程私有,每个线程独立拥有自己的栈 空间大小 通常较大(几G级别),动态扩展 通常较小(几M级别),固定上限 访问效率 较慢,需通过指针间接访问 较快,直接通过栈顶指针操作 异常类型 OutOfMemoryError(内存耗尽) StackOverflowError(栈深度超限) 二、多线程场景下的堆与栈行为分析
1. 栈的线程私有特性(绝对安全)
每个线程启动时,JVM会为其分配独立的虚拟机栈,栈中的局部变量、方法调用栈帧完全隔离:
public class StackThreadDemo { public static void main(String[] args) { // 线程1:栈中保存自己的num变量 new Thread(() -> { int num = 1; try { Thread.sleep(1000); } catch (InterruptedException e) {} System.out.println("线程1的num:" + num); // 输出1 }).start(); // 线程2:栈中保存自己的num变量,与线程1完全无关 new Thread(() -> { int num = 2; System.out.println("线程2的num:" + num); // 输出2 }).start(); } }关键结论:栈内存天然线程安全,不存在多线程竞争问题,因为每个线程的栈是独立的。
2. 堆的共享特性(线程不安全场景)
堆是所有线程共享的内存区域,当多个线程访问堆中的对象时,会出现线程安全问题:
场景1:多线程修改共享对象属性
public class HeapThreadDemo { // 共享对象存储在堆中 static class Counter { int count = 0; } public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); // 10个线程同时修改堆中的count属性 for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { // 非原子操作,会出现线程安全问题 counter.count++; } }).start(); } Thread.sleep(2000); System.out.println("最终count值:" + counter.count); // 大概率小于10000 } }问题原因:
count++包含读取、加1、写入三个步骤,多个线程可能同时读取旧值,导致覆盖。场景2:多线程访问共享静态变量
静态变量存储在堆的方法区中,同样属于共享资源:
public class StaticHeapDemo { // 静态变量存储在堆的方法区 static int staticNum = 0; public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 5; i++) { new Thread(() -> { staticNum += 10; }).start(); } Thread.sleep(1000); System.out.println("staticNum最终值:" + staticNum); // 可能小于50 } }3. 堆的线程安全解决方案
针对堆中共享资源的竞争问题,常用解决方式:
方案 示例代码片段 适用场景 synchronized锁 synchronized (counter) { counter.count++; }通用场景,保证原子性 原子类 AtomicInteger count = new AtomicInteger(0); count.incrementAndGet();简单数值操作,性能更高 Lock锁 ReentrantLock lock = new ReentrantLock(); lock.lock(); try { ... } finally { lock.unlock(); }复杂场景,支持公平锁等特性 三、多线程下堆与栈的典型异常
- 栈异常:StackOverflowError当线程调用方法的深度超过栈的最大容量时抛出,比如递归调用无终止条件:
public class StackOverflowDemo { public static void recursive() { recursive(); // 无限递归 } public static void main(String[] args) { recursive(); // 抛出StackOverflowError } }注意:每个线程的栈独立,一个线程栈溢出不会影响其他线程。
- 堆异常:OutOfMemoryError当堆内存无法分配新对象时抛出,比如创建大量对象:
public class OutOfMemoryDemo { public static void main(String[] args) { List<Object> list = new ArrayList<>(); while (true) { list.add(new Object()); // 不断创建对象,最终抛出OutOfMemoryError } } }注意:堆是共享的,一个线程耗尽堆内存会导致所有线程无法分配新对象。
