“堆”和“栈”并不是数据结构上的Heap跟Stack,而是程序运行中的不同内存空间。
在Java中说到堆栈,我们首先会想到基本数据类型会存放在栈上,引用数据类型存放在堆上,直观感受基本数据类型的使用肯定是会比引用数据类型要快的,那么具体的原因是什么呢,我们下面来分析。
具体分析这个问题从以下两个方面来看:
- 分配效率
- 访问效率
分配效率
栈是操作系统提供的数据结构,计算机会在底层对栈提供支持,而堆是java提供的,是由JVM进行管理,机制比较复杂,所以栈的分配效率比堆的效率高。
除此之外,栈是编译时系统自动分配空间,而堆是动态分配(运行时分配空间),所以栈的速度快。
1. 栈分配的软件优势
栈分配算法简单,所以高效;
堆分配算法相对比较复杂,这里可以我们可以想一想JVM对于堆内存的分配以及回收。
2. 栈分配的硬件优势
主要两点,cache和内存映射。
如果在 栈上分配小块内存,因为cache和内存映射已经建立 ,则效率会非常高,远远优于堆分配。
如果在栈上分配大块内存,在不考虑爆栈的情况下,其实两者效率差不到哪去。因为cache命中和内存映射总是在有限的大小进行的,其在栈中分配的大块内存照样cache不命中,而且映射未建立,所以这样的时间相差其实并不太多。
访问效率
由于Java中的堆内存是封装在操作系统更上一层的,所以我们这里也可以直接看操作系统的行为。
1. 访问操作
1)分配和释放,堆在分配和释放时都要调用函数(MALLOC,FREE),比如分配时会到堆空间去寻找足够大小的空间(因为多次分配释放后会造成空洞),这些都会花费一定的时间,具体可以看看MALLOC和FREE的源代码,他们做了很多额外的工作,而栈却不需要这些。
2)访问时间,访问堆的一个具体单元,需要两次访问内存,第一次得取得指针,第二次才是真正得数据,而栈只需访问一次。另外,堆的内容被操作系统交换到外存的概率比栈大,栈一般是不会被交换出去的。
两次是如何操作的?
有寄存器直接对栈进行访问(esp,ebp),而对堆访问,只能是间接寻址。也就是说,可以直接从地址取数据放至目标地址;使用堆时,第一步将分配的地址放到寄存器,然后取出这个地址的值,然后放到目标地址。
2. 程序局部性原理
栈有专门的寄存器,压栈和出栈的指令效率很高,而堆需要由OS动态调度,堆内存可能被OS调度在非物理内存中,或是申请内存不连续,造成碎片过多等问题。