浅谈我对堆和栈的理解(JVM)

写在前面

  • 近日,本人在参加Java后端面试的时候连续被问到了一个话题,就是“谈一谈你对于堆和栈的理解”。 显然,这个话题并不是一个数据结构的问题。实际上,谈堆和栈的理解,考察的是一个求职者有没有对计算机底层具备深入且举一反三的认识。出色地答出这个问题将会大大提高在面试官心中的印象,从而增大斩获Offer的可能性。
  • 此外,重新正确认识内存分配的机理。从是什么到为什么有一个逻辑上的串联,也对于深入调优和算法设计有着很大的帮助。

在JVM中,堆和栈是如何被分配的

  • 通常来讲,我们可以大致认为:值类型(int, boolean, double, 地址等)被分配在栈上面,而引用类型(数组、Object的)被分配在堆上面。在这里,我们谈论的值类型并非只读、全局静态,对于这些特殊的值类型,JVM中有专门的区域来分配他们的内存。
  • 但在严格意义上来说,堆和栈的分配其实是模糊的:JVM更倾向于在堆空间上进行分配,在有些情况下,即便是值类型也会被分配在堆空间。
  • 此外,我们还可以从另外一个角度来思考值类型和引用类型(作用域):对于一个非全局的值类型来说,它的生命周期开始于方法被调用之后的分配,结束于这个方法return,是线程级别的生命周期。而对象则是以引用传递的方式在方法间迁移、注入,其背后映射的数据内容是生命周期较长的,贯穿整个应用程序,要由GC来对他们进行管理。

为何这样进行分配

  1. 空间大小拓展性:虽然栈和堆都是动态的数据结构,但是在内存上的堆栈之间是存在动态拓展能力区别的。假设堆栈空间足够大,栈只能无限增加元素,实现栈的动态;而堆空间是数组+链表的数据结构模式,其链表具有元素内的动态拓展性,恰好对应引用类型的空间不确定性。
  2. 速度优势:对于计算机硬件来说,对于栈空间是有专门的寄存器来作为硬件支持的,所以调用速度要快于堆空间。在设计上时,我们有意识让大小确定的类型使用栈空间,提高运行效率。
  3. FILO特性:上文提到了栈空间内值类型的生命周期与其所在的方法(函数)相同。那么我们不妨假设一个方法之间复杂调用的情况,试想一个很大的方法深度。最近被使用的方法中的变量,也会最先被释放掉;而最早被调用的方法(举最极端的例子,main())则会被最后释放,恰好符合FILO特性。如此利用栈来作为值类型变量的管理,可以最大程度减小内存指针的游历,从而提高效率。
  4. 线程安全:由于栈具有很强的单线程独享性,所以涉及到线程同步、线程数据共享,都会避免利用栈,转而通过堆空间来完成。

总结

  1. 栈里面存储的是大小确定的值类型,堆中则存储引用类型,核心差异是动态与否。
  2. 栈的结构特性高度符合函数调用时的内存分配、释放顺序需求。
  3. 栈空间具有直接的访问速度优势,但是堆空间和GC机制有着更高的配合效率。
  4. 栈空间的作用域在线程(一次方法调用),而堆的作用域在整个应用程序。
  5. 堆是线程安全的,栈则不是。

版权声明:本文为jinshangheng原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。