概述
本文分三部分,描述有所重叠,但可以让你对栈与堆有一个比较清晰、全面的认识
heap 和 stack是什么
堆栈是两种数据结构。堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。==在单片机应用中,堆栈是个特殊的存储区,主要功能是暂时存放数据和地址==,通常用来保护断点和现场。
要点:
堆:队列优先,先进先出(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中。
下面是从编程的角度对栈与堆的描述:
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.