目录
- 1- 引言:虚拟机栈
- 1-1 虚拟机栈是什么?(What)
- 1-2 为什么用虚拟机栈?虚拟机栈的作用 (Why)
- 2- ⭐核心:栈的常见问题(How)
- 2-1 方法内的局部变量是否线程安全?
- 线程不安全的局部变量
- 2-2 什么情况会导致栈内存溢出?
- 2-3 栈和堆的区别?
- 3- 小结:
- 3-1 什么是虚拟机栈?
- 3-2 垃圾回收是否涉及栈内存?
- 3-3 栈内存分配越大越好吗?
- 3-4 方法内的局部变量是否线程安全?
- 3-5 什么情况会导致栈内存溢出?
- 3-6 栈和堆的区别?
1- 引言:虚拟机栈
1-1 虚拟机栈是什么?(What)
- 虚拟机栈是每个线程独有的: Java Virtual Machine Stacks (Java虚拟机栈),每个线程运行时候所需要的内存称为虚拟机栈,是先进后出的。栈内存也是线程安全的(因为其是每个线程独有的)。
- 假设有栈帧来了,其会压入栈底,再有栈帧来了其会依次压入。
- 栈帧: 每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存
- 活动栈帧: 每个线程只能有一个活动栈帧,对应着当前正在执行的方法
1-2 为什么用虚拟机栈?虚拟机栈的作用 (Why)
- 方法调用管理:
- 每个方法调用都会在虚拟机栈中创建一个新的栈帧,栈帧中包含了方法的局部变量、操作数栈、动态链接和方法返回地址等信息。通过这种机制,虚拟机能够正确地执行方法调用和返回操作。
- 局部变量存储:
- 方法中的局部变量会存储在对应的栈帧中。由于栈帧是线程私有的,因此局部变量是线程安全的。
2- ⭐核心:栈的常见问题(How)
2-1 方法内的局部变量是否线程安全?
线程内的局部变量是否线程安全,取决于该变量是否被多个线程共享使用。
线程安全的局部变量
- 在方法
m1
中,StringBuilder sb
是一个局部变量,仅在方法m1
内部使用。由于每个线程调用m1
时都会创建自己独立的sb
变量,因此m1
方法中的局部变量sb
是线程安全的。
线程不安全的局部变量
- 在方法
m2
中,StringBuilder sb
作为参数传递进来,可能会被多个线程同时访问。图中显示在main
方法中,一个新线程会调用m2
方法并传入sb
变量。这种情况下,sb
变量被多个线程共享使用,因此m2
方法中的局部变量sb
是线程不安全的。 - 在方法
m3
中,StringBuilder sb
虽然是在方法内部创建的局部变量,但方法返回了这个变量的引用。此时,外部方法(例如main
方法)可以共享和修改这个返回的sb
变量。如果多个线程调用m3
方法并使用返回的sb
变量,也会导致线程不安全。
结论
- 线程安全:当局部变量在方法内部使用,并且没有逃离方法的作用范围,它是线程安全的。
- 线程不安全:如果是局部变量引用了对象,并逃离方法的作用范围(当局部变量通过参数传递或者作为返回值返回),导致可能被多个线程共享使用,此时是线程不安全的。
2-2 什么情况会导致栈内存溢出?
① 栈帧过多导致栈内存溢出,典型的问题:递归调用
② 栈帧过大导致栈内存溢出
- 局部变量过多:当一个方法中定义了过多的局部变量,每个局部变量都需要在栈中分配空间,导致栈帧过大。如果栈帧大小超过了虚拟机栈的最大限制,将会导致栈内存溢出。
- 大对象的局部变量:如果方法中包含大对象作为局部变量,这些对象会占用大量的栈空间,导致栈帧过大,进而可能导致栈内存溢出。
2-3 栈和堆的区别?
①用途和存储内容不同
- 栈内存:用于存储局部变量和方法调用。每当一个方法被调用时,都会在栈中创建一个新的栈帧,用于存储该方法的局部变量、操作数栈、动态链接和方法返回地址等信息。
- 堆内存:用于存储所有的 Java 对象和数组。堆是一个被所有线程共享的内存区域,用于存放在运行时创建的对象和数组。
②线程安全性:
- 栈内存:是线程私有的,每个线程都有自己的栈,因此栈内存是线程安全的,不需要考虑多线程并发访问的问题。
- 堆内存:是线程共享的,多个线程可以访问同一个对象,因此需要考虑线程安全问题,可能需要同步机制来避免数据竞争。
③内存管理:
- 栈内存:由系统自动分配和释放。每当方法调用时,栈帧自动分配;方法结束时,栈帧自动销毁,内存释放。
- 堆内存:由垃圾回收器(GC)管理。Java 程序中对象的分配和释放由垃圾回收器负责,当对象不再被引用时,GC 会自动回收这些对象所占用的内存。
④存储大小:
- 栈内存:通常较小且固定,每个线程有一个独立的栈,栈大小在程序启动时由虚拟机设置。
- 堆内存:通常较大且可动态扩展,整个 Java 应用程序共享一个堆内存区域,堆大小可以在启动时通过 JVM 参数进行配置。
⑤内存溢出:
- 栈内存:栈的内存不足会抛出
StackOverflowError
,常见原因是递归调用过深或方法调用层次过多,导致栈帧数量过多。 - 堆内存:堆的内存不足会抛出
OutOfMemoryError
,常见原因是创建了过多的对象,或对象占用的内存过大,导致堆内存耗尽。