你的位置:首页 > 软件开发 > Java > JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结

JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结

发布时间:2016-02-22 01:00:09
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下:堆栈是栈JVM栈和本地方法栈划分Java中的堆,栈和c/c++中的堆,栈数据结构层面的堆,栈os层面的堆,栈JVM的堆,栈和os如何对应为啥方法的 ...

JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结

俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下:

  • 堆栈是栈
  • JVM栈和本地方法栈划分
  • Java中的堆,栈和c/c++中的堆,栈
  • 数据结构层面的堆,栈
  • os层面的堆,栈
  • JVM的堆,栈和os如何对应
  • 为啥方法的调用需要栈

  属于月经问题了,正好碰上有人问我这类比较基础的知识,无奈我自觉回答不是有效果,现在深入浅出的总结下:

前一篇文章总结了:JVM 的内存主要分为3个分区

   堆栈是啥?是堆还是栈?

   之前初学c++的时候被人误导过,说堆栈是堆……其实这个是翻译的误读,堆栈,其实应该翻译成栈更合适,和堆区分开来,因为英文的stack就是堆栈的意思, 位于RAM(Random Access Memory,随机访问存储区),速度仅次于寄存器。存放基本变量和引用,存在栈中的数据可以共享。但是,栈中的数据大小和生存周期必须确定,这是栈的缺点

  堆栈不是堆,是栈。堆是存放了所有的java对象(逃逸分析除外)。


  
  数据结构里面。

stack,中文翻译为堆栈,其实指的是栈,这里讲的是数据结构的栈,不是内存分配里面的堆和栈。栈是先进后出的数据的结构,好比你碟子一个一个堆起来,最后放的那个是堆在最上面的。

栈数据结构比较简单。heap翻译为堆,是

一种有序的树。


 

 

  哦了。前面几个问题已经得出这样的结论:栈和堆都是用来从底层操作系统中获取内存的。在多线程环境下每一个线程都可以有他自己完全的独立的栈,但是他们共享堆。并行存取被堆控制而不是栈。

  • 堆:包含一个链表来维护已用和空闲的内存块

JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结  

在堆上新分配(用 new 或者 malloc)内存是从空闲的内存块中找到一些满足要求的合适块。这个操作会更新堆中的块链表。这些元信息也存储在堆上,经常在每个块的头部一个很小区域。堆增加新块通常从低地址向高地址扩展,也就是说堆是向上增长的!因此可以认为堆随着内存分配而不断的增加大小。如果申请的内存大小很小的话,通常从底层操作系统中得到比申请大小要多的内存。申请和释放许多小的块可能会产生如下状态:在已用块之间存在很多小的浪费的空闲块……进而导致申请大块内存失败,虽然空闲块的总和足够,但是空闲的小块是零散的,不能满足申请的大小,这叫做“内存碎片”。当旁边有空闲块的已用块被释放时,新的空闲块可能会与相邻的空闲块合并为一个大的空闲块,这样可以有效的减少“碎片”的产生。

  堆的管理依赖于运行时环境,C 使用 malloc ,free,C++ 使用 new 和delete,但是很多语言有垃圾回收机制,比如Java的GC。

  • 栈:栈经常与 sp 寄存器一起工作,最初 sp 指向栈顶(栈的高地址)。栈是向下增长的!

JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结

CPU 用 push 指令来将数据压栈,用 pop 指令来弹栈。当用 push 压栈时,sp 值减少(向低地址扩展)。当用 pop 弹栈时,sp 值增大。存储和获取数据都是 CPU 寄存器的值

    • 函数被调用时,CPU使用特定的指令把当前的 IP 压栈,接下来将调用函数的地址赋给 IP,让cpu去调用函数。当函数返回时,旧的 IP 被弹栈,CPU 继续去函数调用之前的代码。
    • 当进入函数时,sp 向下扩展,扩展到确保为函数的局部变量留足够大小的空间。如果函数中有一个 32-bit 的局部变量会在栈中留够四字节的空间。当函数返回时,sp 通过返回原来的位置来释放空间。
      • 如果函数有参数的话,在函数调用之前,会将参数压栈。函数中的代码通过 sp 的当前位置来定位参数并访问它们。
      • 函数嵌套调用,每一次新调用的函数都会分配函数参数,返回值地址、局部变量空间、嵌套调用的活动记录都要被压入栈中。函数返回时,按照正确方式的撤销。

  栈要受到内存块的限制,不断的函数嵌套……为局部变量分配太多的空间,可能会导致栈溢出。当栈中的内存区域都已经被使用完之后继续向下写(低地址),会触发一个 CPU 异常。这个异常接下会通过语言的运行时转成各种类型的栈溢出异常。总的来说,栈以更低层次的特性与处理器架构紧密的结合到一起,当堆不够时可以扩展空间。但是,扩展栈通常来说是不可能的,因为在栈溢出的时候,执行线程就**作系统关闭了,这已经太晚了。

   现在可以回答这几个问题:

  在通常情况下由操作系统(OS)和语言的运行时(runtime)控制吗?

   如前所述,堆和栈是一个统称,可以有很多的实现方式。计算机程序通常有一个栈叫做调用栈,用来存储当前函数调用相关的信息(比如:主调函数的地址,局部变量),因为函数调用之后需要返回给主调函数。栈通过扩展和收缩来承载信息。实际上,程序不是由运行时来控制的,它由编程语言、操作系统甚至是系统架构来决定。堆是在任何内存中动态和随机分配的(内存的)统称;也就是无序的。内存通常由操作系统分配,通过应用程序调用 API 接口去实现分配。在管理动态分配内存上会有一些额外的开销,不过这由操作系统来处理。

  它们的作用范围是什么?

  调用栈是一个低层次的概念,就程序而言,它和“作用范围”没什么关系。就高级语言而言,语言有它自己的范围规则。一旦函数返回,函数中的局部变量会直接释放。在堆中,也很难去定义。作用范围是由操作系统限定的,但是编程语言可能增加它自己的一些规则,去限定堆在应用程序中的范围。体系架构和操作系统是使用虚拟地址的,然后由处理器翻译到实际的物理地址中,还有页面错误等等。它们记录那个页面属于那个应用程序。不过你不用关心这些,因为你仅仅在编程语言中分配和释放内存,和一些错误检查(出现分配失败和释放失败的原因)。

  它们的大小由什么决定?

  依赖于语言,编译器,操作系统和架构。栈通常提前分配好了,因为栈必须是连续的内存块。语言的编译器或者操作系统决定它的大小。不要在栈上存储大块数据,这样可以保证有足够的空间不会溢出,除非出现了无限递归的情况或者其它。堆是任何可以动态分配的内存的统称。它的大小是变动的。在现代处理器中和操作系统的工作方式是高度抽象的,因此你在正常情况下不需要担心它实际的大小,除非你必须要使用你还没有分配的内存或者已经释放了的内存。

  哪个更快一些?

  栈更快因为所有的空闲内存都是连续的,因此不需要对空闲内存块通过列表来维护。只是一个简单的指向当前栈顶的指针。编译器通常用一个专门的、快速的寄存器SP来实现。更重要的一点是,随后的栈上操作会遵循局部性原理。

  JVM的栈如何对应os?

  以linux 中一个进程的虚拟内存分布为例:

JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结

图中0号地址在最下边,越往上内存地址越大。以32位操作系统为例,一个进程可拥有的虚拟内存地址范围为0-2^32。分为两部分,一部分留给kernel使用(kernel virtual memory),剩下的是进程本身使用, 即图中的process virtual memory。普通Java 程序使用的就是process virtual memory。上图中最顶端的一部分内存叫做user stack. 这就是栈stack,32位的栈顶指针寄存器是esp,中间有 runtime heap。就是堆,注意他们和数据结构里的stack 和 heap 不是一回事。前面总结了,stack 是向下生长的,heap是向上生长的。当程序进行函数调用时,每个函数都在stack上有一个 call frame(帧)。  小结,总结了那么多,现在最后一个问题:为啥方法的调用需要栈

原标题:JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结

关键词:jvm

jvm
*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。