外观
JVM 栈和堆的区别
功能和用途
JVM
栈:主要用于执行Java
方法。每个线程在创建时都会分配一个独立的JVM
栈,栈中包含多个栈帧(Stack Frame
),每调用一个方法就会创建一个新的栈帧并压入栈顶,方法执行完毕后该栈帧会从栈中弹出。栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,它是方法执行的基本单位。- 堆:是
Java
虚拟机所管理的内存中最大的一块区域,主要用于存储对象实例和数组。Java
程序中通过new
关键字创建的对象都存放在堆中,几乎所有的对象实例都在堆上分配内存。堆是所有线程共享的内存区域,多个线程可以同时访问堆中的对象。
存储内容
JVM
栈- 存储局部变量。局部变量包括基本数据类型(如
int
、char
等)和对象引用(即指向堆中对象的地址)。局部变量表的大小在编译时就已经确定,在方法执行过程中,局部变量的生命周期与方法的执行周期一致。 - 操作数栈用于在方法执行过程中进行数据的计算和操作,例如进行算术运算、方法调用时的参数传递等。
- 存储局部变量。局部变量包括基本数据类型(如
- 堆:存储对象实例和数组。对象实例包含了对象的所有属性和方法信息,数组则是一组相同类型的数据的集合。堆中的对象可以被多个栈中的引用所指向,从而实现数据的共享和传递。
生命周期
JVM
栈:与线程的生命周期紧密相关。每个线程的JVM
栈在该线程创建时被分配,在线程结束时被销毁。栈中的每个栈帧也有自己的生命周期,随着方法的调用和返回而创建和销毁。- 堆:从
JVM
启动时就被创建,直到JVM
关闭才会被销毁。堆中的对象生命周期由垃圾回收机制来管理,当一个对象不再被任何引用指向时,垃圾回收器会在合适的时机将其占用的内存回收。
内存分配和回收方式
JVM
栈:内存分配和回收是自动进行的,遵循后进先出(LIFO
)的原则。当方法被调用时,会为该方法创建一个新的栈帧并压入栈顶,方法执行完毕后,该栈帧会自动从栈中弹出,其所占用的内存也会被释放。栈的内存分配和回收速度非常快,因为只需要移动栈指针即可。- 堆:内存分配和回收相对复杂。对象的分配需要在堆中找到一块足够大的连续内存空间,这可能涉及到内存碎片的管理和分配算法的选择。堆的内存回收由垃圾回收器负责,垃圾回收器会定期扫描堆中的对象,标记出不再使用的对象,并将其占用的内存回收。垃圾回收过程可能会导致程序的暂停,影响程序的性能。
内存空间大小
JVM
栈:每个线程的栈空间大小通常是固定的,可以通过-Xss
参数来设置。一般来说,栈空间的大小相对较小,通常在几百KB
到几MB
之间。如果栈深度过大,例如出现无限递归调用的情况,可能会导致栈溢出异常(StackOverflowError
)。- 堆:堆的大小可以通过
-Xms
(初始堆大小)和-Xmx
(最大堆大小)等参数来设置。堆的空间通常比栈大得多,根据不同的应用场景和系统配置,可以设置为几百MB
甚至数GB
。如果堆空间不足,会导致内存溢出异常(OutOfMemoryError
)。
线程安全性
JVM
栈:是线程私有的,每个线程都有自己独立的栈空间,因此不存在线程安全问题。不同线程之间的栈帧和局部变量不会相互影响。- 堆:是线程共享的内存区域,多个线程可以同时访问堆中的对象。因此,在多线程环境下,对堆中对象的访问需要进行同步控制,以避免数据不一致和其他并发问题。