sf04_操作系统中 heap 和 stack 的区别

 概述

本文分三部分,描述有所重叠,但可以让你对栈与堆有一个比较清晰、全面的认识

heap 和 stack是什么

严格地讲,程序运行的堆栈,实际上是指栈,栈是一种数据结构,并且是数据结构中的线性结构,可以用数组来实现,先进后出; 就像一个桶,来数据就往里面放,取数据或销毁数据自然从最上面取,也就是取最新的数据;堆不是数据结构,是一种操作系统内存的分配方式,每一次分配内存的地址都记录于对应的栈中,变量生命周期的结束,内存的销毁是从栈的顶部开始,就是分配一个地址就把这个地址放进桶里,最早分配的内存地址在桶的底部,最后分配的内存地址在桶的顶部,使用或销毁的操作皆发生在栈顶,所以堆自然就变成先进先出。所以程序开发中提到的堆栈中的堆,是程序运行时内存的分配方式,本质上没有对堆定义什么线性或非线性的数据结构。

通俗来讲,堆栈是两种数据结构。这里的堆并不是程序中运行的堆,是数据结构层面上的堆,代表了数据之间的一种关系。

要点:
堆:队列优先,先进先出(FIFO—first in first out)。
栈:先进后出(FILO—First-In/Last-Out)。

heap 和 stack有什么区别
一、堆栈空间分配区别:

  1、栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
  2、堆(操作系统):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。

二、堆栈缓存方式区别:

  1、栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放;

  2、堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。

三、堆栈数据结构区别:

  堆(数据结构):堆可以被看成是一棵树,如:堆排序;

  栈(数据结构):一种先进后出的数据结构。

Java中栈和堆的区别:

  栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

  在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。

  堆内存用来存放由new创建的对象和数组,在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。

Java中变量在内存中的分配:

  1、类变量(static修饰的变量):在程序加载时系统就为它在堆中开辟了内存,堆中的内存地址存放于栈以便于高速访问。静态变量的生命周期–一直持续到整个”系统”关闭。

  2、实例变量:当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量(比如说类实例),然后根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的”物理位置”。 实例变量的生命周期–当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并不是马上就释放堆中内存。

  3、局部变量:局部变量,由声明在某方法,或某代码段里(比如for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离作用域,内存立即释放。
这里要涉及到Java内存问题,可以参考:Java的内存机制

Java实现一个简单堆栈实例代码:
堆栈(Stack)是一种常见的数据结构,符合后进先出(First In Last Out)原则,通常用于实现对象存放顺序的逆序。栈的基本操作有push(添加到堆栈),pop(从堆栈删除),peek(检测栈顶元素且不删除)

/**
 * Created by Frank
 */
public class ToyStack {
    /**
     * 栈的最大深度
     **/
    protected int MAX_DEPTH = 10;

    /**
     * 栈的当前深度
     */
    protected int depth = 0;

    /**
     * 实际的栈
     */
    protected int[] stack = new int[MAX_DEPTH];

    /**
     * push,向栈中添加一个元素
     *
     * @param n 待添加的整数
     */
    protected void push(int n) {
        if (depth == MAX_DEPTH - 1) {
            throw new RuntimeException("栈已满,无法再添加元素。");
        }
        stack[depth++] = n;
    }

    /**
     * pop,返回栈顶元素并从栈中删除
     *
     * @return 栈顶元素
     */
    protected int pop() {
        if (depth == 0) {
            throw new RuntimeException("栈中元素已经被取完,无法再取。");
        }

        // --depth,dept先减去1再赋值给变量dept,这样整个栈的深度就减1了(相当于从栈中删除)。
        return stack[--depth];
    }

    /**
     * peek,返回栈顶元素但不从栈中删除
     *
     * @return
     */
    protected int peek() {
        if (depth == 0) {
            throw new RuntimeException("栈中元素已经被取完,无法再取。");
        }
        return stack[depth - 1];
    }
}

---------------------
作者:一只小羊羔
来源:CSDN
原文:https://blog.csdn.net/guan_sen/article/details/78769487
上面的文章是转载的,这篇文章写的不错,个人修改了一部分,既然标题说的是操作系统的的堆和栈,那再补充下面的一些内容

图片来自《Linux性能优化》这本书,以这个为基础再次说明一下

程序代码的运行,在操作系统中是以进程的方式进行的,系统动态地为进程分配内存,这是因为进程耗费的资源也是有多有少;

比如上面的这个Java类,代码部分将存放于进程的Text segment区域,并且是位于进程内存地址的开头部分;

紧接着存储的是变量的初始化值,比如上面程序MAX_DEPTH的值10,位于Data segment的data区域,静态变量的值在Java值中要比实例的值更早地初始化,当然也是存储于Data segment区域中;

其次是,0零的初始化,比如int i;没有对i进行赋值时,默认初始化为0,存储于Data segment的BSS区域;

其次是,方法、宏、函数等关联的存储位置,这是一个动态分配的内存区域,存储地址由0x的低位向高位地址扩展,位于Data segment的heap区域,注意这些方法、宏、函数的代码位于进程地址的开头部分,此处的head是它们关联的内存空间、是运行中所占用的内存空间

最后是,程序中变量、函数参数、函数的返回地址的存储区域,位于Stack segment区域,位于进程地址的最底部,向上自动扩展;

如果是简单类型,比如int,存储于Stack segment区域,如果是复合类型,比如字符串,对象等,则stack segment存储的是这些复合类型的地址,也就是说地址“引用”,或者说是地址入口等等(就是通过这个内存中的位置,找到相应的对象),而这些字符串、对象的内存空间的则是位于heap中。

 

使用大白话补充说明一下,之前大家都在说堆栈,还以为堆是堆、栈是栈,离的很远;但实际上,一个程序有主函数与其他函数,它们的内存的分配是按进程来分的,一个进程中有一个栈,一个堆,分别位于一段内存地址的两端,需要内存空间时,都向中间要。变量地址肯定在栈里了,堆的代码,是代码,在被调用的过程中,不管被调用多少次(比如递归调用)是不会变的,变量的地址也不会变了,变的是什么?是变量的值!对比上面的图,静态Text就是程序代码了,动态区域就是用来计算了,复制、传递、加减乘除……一系列计算,不仅消耗内存还耗费CPU,计算完这个函数后,释放内存,销毁这个地址,然后开始处理栈中的下一个地址……直到栈为空,程序运行结束,主函数内存释放。这么看来,进程使用的内存地址是连续的?实际上进程使用的内存叫虚拟内存,是实际物理内存的映射,虚拟内存是连续的,所对应的物理内存就不一定了。

另外,连续与不连续指的是内存,而不是磁盘,没错,数据结构中的物理结构,更多指的是内存这种存储器,内存中数据连续,磁盘中一定连续吗?未必。比如机械盘与SSD就有很大的差异,磁盘硬件只要能满足读写需求,能稳定存放数据即可,未来磁盘会向何方向发展,都不影响它的数据在内存中的结构;而常说的内存,指是是虚拟内存,虚拟内存是什么?是由物理内存映射的一段空间,每个空间有个编号,从0开始,32位操作系统就是2^32个空间,从0-2^32-1个编号,也就是说32位操作系统最大4G内存。空间内的编号,通常有两个含义,变量的值、变量的地址。前者通常是一种语言的简单类型,比如整数、字符、布尔,后面则是表示一个位置,另外一个空间的位置。比如10001号空间放的数值是3,你把10001这个地址放进了30003号空间中,那么就是表示30003号空间存放着数值3所在10001号空间的编号,你从30003空间中读取10001这个数值,又知道10001只是一个空间的编号,再去10001空间中取值,就得到了数值3.一个空间中存放的是变量的值,还是地址,则取决于你如何定义一个变量。

为了提升读取磁盘数据的速度,在数据库系统中,会将分散在磁盘上的数据,按顺序存入buffer中(比如mysql中的read_rnd_buffer_size参数)。

 

下面是从编程的角度对栈与堆的描述:

Both the stack and the heap are parts of memory that are available to your code to use at runtime, but they are structured in different ways. The stack stores values in the order it gets them and removes the values in the opposite order. This is referred to as last in, first out. Think of a stack of plates: when you add more plates, you put them on top of the pile, and when you need a plate, you take one off the top. Adding or removing plates from the middle or bottom wouldn’t work as well! Adding data is called pushing onto the stack, and removing data is called popping off the stack.

The stack is fast because of the way it accesses the data: it never has to search for a place to put new data or a place to get data from because that place is always the top. Another property that makes the stack fast is that all data on the stack must take up a known, fixed size.

Data with a size unknown at compile time or a size that might change can be stored on the heap instead. The heap is less organized: when you put data on the heap, you ask for some amount of space. The operating system finds an empty spot somewhere in the heap that is big enough, marks it as being in use, and returns a pointer, which is the address of that location. This process is called allocating on the heap, sometimes abbreviated as just “allocating.” Pushing values onto the stack is not considered allocating. Because the pointer is a known, fixed size, you can store the pointer on the stack, but when you want the actual data, you have to follow the pointer.

Think of being seated at a restaurant. When you enter, you state the number of people in your group, and the staff finds an empty table that fits everyone and leads you there. If someone in your group comes late, they can ask where you’ve been seated to find you.

 Accessing data in the heap is slower than accessing data on the stack because you have to follow a pointer to get there. Contemporary processors are faster if they jump around less in memory. Continuing the analogy, consider a server at a restaurant taking orders from many tables. It’s most efficient to get all the orders at one table before moving on to the next table. Taking an order from table A, then an order from table B, then one from A again, and then one from B again would be a much slower process. By the same token, a processor can do its job better if it works on data that’s close to other data (as it is on the stack) rather than farther away (as it can be on the heap). Allocating a large amount of space on the heap can also take time.

When your code calls a function, the values passed into the function (including, potentially, pointers to data on the heap) and the function’s local variables get pushed onto the stack. When the function is over, those values get popped off the stack.

 

posted @ 2019-02-14 10:01  方诚  阅读(553)  评论(0编辑  收藏  举报