一直以来都在接受一个论调:在Java的内存管理中,基础类型变量保存在栈中,对象类型保存在堆中。 这实际上是一个很粗的论调,会引发不少错误的理解。java
这篇博客的产出,仅是由于脑子里偶然间冒出来的一个问题:java栈内存的内存回收是怎么作的? 由于这个问题,引起了一系列的小问题:全部的基础类型变量都保存在栈中吗?栈和堆上都在运行着咱们熟悉的GC吗?怎么都感受GC算法在栈这种结构上无法跑啊……堆内存,栈内存到底是什么样的?程序员
这里我很是推荐你阅读一本关于java虚拟机的书:《深刻理解java虚拟机》 -- 周志明算法
Java栈内存的内存回收是怎么作的?彷佛由于这个问题过小白了,大部分关于JAVA GC的博客都不会特别的指明,GC是运行在JAVA内存堆上的。但也是由于这个缘由,对于一些基础知识不太好的小白(好比我),看多了这种博客以后,想固然的认为了GC负责了整个java内存的管理,好像~栈内存也是由GC进行回收的……(毕竟栈的全名还叫堆栈呢)bash
当有一天我翅膀硬了点,忽然醒悟,栈这种只能在一头进出的数据结构,跑的是什么GC算法???如此简单的数据结构,也不须要GC了吧?数据结构
终于在查阅了不少资料后,确认了一个问题:GC运行在JAVA堆上,负责java堆内存的回收。优化
那么确认这个大前提下,后面连续不断的问题就都冒出来了:栈内存是怎么进行内存回收的?全部的基础类型变量都保存在栈内存中吗?spa
栈嘛,仅容许在线性表的一端进行插入和删除运算,因此分配内存就在栈顶插进去一块,回收,就从栈顶取出来。线程
这种管理结构,从方法的角度其实很容易理解,咱们看下面一段伪代码:code
public void main(){
method1();
}
public void method1(){
...
method2();
}
public void method2(){
...
method3();
}
public void method3(){
...
//end
}
复制代码
代码很简单,main方法调用了method1,method2调用了method3,method3结束了。用图的形式更直观:cdn
从main方法开始执行,各方法依次入栈,每一个方法执行的同时,都会建立一个栈帧,而后进入虚拟机栈。
method3方法执行结束后,各方法(栈帧)依次出栈:
这就是java栈内存,用《深刻理解java虚拟机》一段话来描述:
java虚拟机栈也是线程私有的,他的生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模式:每一个方法在执行的同时都会建立一个栈帧用于存储局部变量表,操做数栈,动态连接,方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。
OK,到这里咱们基本上能够解决第二个问题:java栈内存的内存回收就是利用方法和栈的特性来作的!
那么第三个问题:全部的基础类型变量都保存在栈中吗?
其实这个问题已经不太须要再查资料验证了,咱们本身就能够分析出结果。 栈内存主要描述的是java方法执行的内存模型,一个方法内部的局部变量生命周期跟随方法的开始和结束,管理起来很是简单,因此方法内部的局部变量保存在栈中没毛病! 可是对象内部的基础类型成员变量呢?这个生命周期就比较复杂了,有明确的开始却没有明确的结束,放到栈中明显没法管理。因此,只有方法内的基础类型变量才会保存在栈中!
第四个问题:咱们知道栈的回收效率和速度远大于堆,为何这一块的管理如此简单快速呢?
继续引用《深刻理解java虚拟机》一段话来描述java栈内存:
常常有人把java内存区分为堆内存和栈内存,这种分法比较粗糙,java内存区域的划分实际上远比这发杂。这种划分方式的流行只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块。其中所指的“栈”就是如今讲的虚拟机栈,或者说是虚拟机栈中局部变量表部分。
局部变量表存放了编译器可知的各类基本数据类型、对象引用和returnAddress类型。 其中64位长度的long和double类型的数据会占用2个局部变量空间(slot),其他的数据类型只占用1个。局部变量表所需的内存空间在编译期完成分配,当进入一个方法时,这个方法须要在帧中分配多大的局部变量空间是彻底肯定的,在方法运行期间不会改变局部变量表的大小。
第五个问题:如此简单高效的栈内存,会内存溢出吗?
虽然大部分虚拟机的栈均可以动态扩展,但也并非无限的。虽然栈溢出的状况比较小,但当有大量方法调用时很是有可能引发栈溢出,很是典型的案例就是递归调用。
解决了两个常见的误解或没有关注的点:GC运行在栈仍是堆仍是二者都有;全部基本类型变量都保存在栈中吗?
java GC仅运行在堆内存上,回收效率较低。 栈内存仅描述了java方法的内存模型,每一个方法所需的内存空间在编译器就已经确认,利用栈的特性完成内存分配和回收。 基础类型变量仅方法内的局部变量保存在栈内存中,成员变量保存在堆中。
虽然没有定量的标准,也没有办法得出优化先后的对比量。但在开发中,咱们应该有意识的减小成员变量,若是数据能够在方法间传递,就没有必要利用成员变量作中间状态保存。同时业务开发避免方法的递归调用。