一直不理解堆栈的概念,很多视频课程讲解又不全面,学习代码的数据结构和算法成为了我最多困惑的地方。最近闲暇,查了下资料,基于Java语言整理下内存区域划分的基础知识。
由于Java程序是交由JVM执行的,所以在谈Java内存区域划分的时候事实上是指JVM内存区域划分。在讨论JVM内存区域划分之前,先来看一下Java程序具体执行的过程:
Java程序执行过程
运行时数据区模块
了解jvm的结构之前,有必要先来了解一下操作系统的内存基本结构
操作系统存储体系
以上是操作系统存储层次【CPU <--- > 寄存器<--- > 缓存(最多三级缓存)<--- >内存<--- >磁盘缓存<--- >固定磁盘存储<--- >可移动存储介质】的部分展示。
操作系统内存布局:
操作系统内部布局
操作系统内存的堆栈:
栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆区 (heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表
栈是为执行线程留出的内存空间(当线程创建的时候,操作系统会为每个系统级的线程分配栈);
栈顶会为局部变量和数据预留块,当函数执行完毕,块就没有用了,可能在下次的函数调用的时候再被使用,栈通常都是采用后进先出的方式预留空间;因此最近的保留块通常最先被释放,从栈中释放块不过是指针的偏移(这里和数据结构中的栈的意思类似)
堆的数据结构并不是由系统支持的,而是由函数库提供的基本的malloc/realloc/free函数维护了一套内部的堆数据结构(所以才需要程序员自己释放内存,否则会造成内存泄漏);
堆包含了一个链表来维护已用和空闲的内存块;
申请内存:
当程序需要在堆中分配内存的时候,会从内部堆中寻找可用的内存空间,通过链表找到符合大小的内存块(链表遍历的方向是由低地址指向高地址),但是由于堆是不连续的内存区域,当找不到合适的内存区域的时候,则会利用系统调用来动态增加程序数据段的内存大小;
释放内存:
当系统受到程序的释放内存的申请的时候,会遍历该链表,寻找第一个空间大于所申请的堆结点,然后该结点会从链表中删除,并将该结点的空间释放给内存,这片内存空间又会返回到堆结构中,会经过内存块的组合,以便适合下次内存分配申请;(这里面如果没有管理内存分配在释放内存时很容易会造成内存碎片)
为什么jvm的内存是分布在操作系统的堆中呢??因为操作系统的栈是操作系统管理的,它随时会被回收,所以如果jvm放在栈中,那java的一个null对象就很难确定会被谁回收了,那gc的存在就一点意义都没有了,而要对栈做到自动释放也是jvm需要考虑的,所以放在堆中就最合适不过了。
操作系统+jvm的内存简单布局JVM 的内存主要分为3个分区
堆区(Heap)-- 只存对象(数组)本身(引用类型的数据),不存基本类型和对象的引用。JVM只有一个堆区,这个“堆”是动态内存分配意义上的堆——用于管理动态生命周期的内存区域。JVM的堆被同一个JVM实例中的所有Java线程共享,它通常由某种自动内存管理机制所管理,这种机制通常叫做“垃圾回收”(garbage collection,GC)。JVM规范并不强制要求JVM实现采用哪种GC算法。
栈区(Stack)-- 栈中只保存基础数据类型的对象和对象引用。每个线程一个栈区,每个栈区中的数据都是私有的,其他栈不能访问。栈内有帧(方法调用会生成栈帧)分三个部分:基本类型变量区,执行环境上下文,操作指令区。
方法区 -- 又叫静态区,跟堆一样,被所有线程共享。方法区包含所有的class和static变量。方法区包含的都是在整个程序中永远唯一的元素。如:class,satic。
运行时jvm的处理
细化到增加jvm内部的处理和pc寄存器的配合时,可见,无论是在虚拟机中还是在我们虚拟机所寄宿的操作系统中功能目的是一致的,计算机上的pc寄存器是计算机上的硬件,本来就是属于计算机,计算机用pc寄存器来存放“伪指令”或地址,而相对于虚拟机,pc寄存器它表现为一块内存(一个字长,虚拟机要求字长最小为32位),虚拟机的pc寄存器的功能也是存放伪指令,更确切的说存放的是将要执行指令的地址,它甚至可以是操作系统指令的本地地址,当虚拟机正在执行的方法是一个本地方法的时候,jvm的pc寄存器存储的值是undefined,所以应该很明确的知道,虚拟机的pc寄存器是用于存放下一条将要执行的指令的地址(字节码流)。
当一个classLoder启动的时候,classLoader的生存地点在jvm中的堆,然后它会去主机硬盘上将A.class装载到jvm的方法区,方法区中的这个字节文件会被虚拟机拿来new A字节码(),然后在堆内存生成了一个A字节码的对象,然后A字节码这个内存文件有两个引用一个指向A的class对象,一个指向加载自己的classLoader,如下图。
image.png
方法区中的字节码内存块,除了记录一个class自己的class对象引用和一个加载自己的ClassLoader引用之外,还记录了以下信息。
image.png
从上面的图,不难发现,原来jvm的设计的模型其实就是操作系统的模型,基于操作系统的角度,jvm就是个java.exe/javaw.exe,也就是一个应用,而基于class文件来说,jvm就是个操作系统,而jvm的方法区,也就相当于操作系统的硬盘区,所以他也叫permanent区,因为这个单词是永久的意思,也就是永久区,我们的磁盘就是不断电的永久区。而java栈和操作系统栈是一致的,无论是生长方向还是管理的方式,至于堆,虽然概念上一致目标也一致,分配内存的方式也一直(new,或者malloc等等),但是由于他们的管理方式不同,jvm是gc回收,而操作系统是程序员手动释放,所以在算法上有很多的差异。
堆栈的概念有2种,通常除了内存上面的概念,还有数据结构里面的概念,两者不完全相同。简单的概念区分如下:
栈(操作系统-内存):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈, 他们通常都是被调用时处于存储空间中,调用完毕立即释放
堆(操作系统-内存): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些
堆(数据结构):堆可以被看成是一棵树,如:堆排序
栈(数据结构):一种后进先出的数据结构
参考资料:
JVM与操作系统
https://zhuanlan.zhihu.com/p/44401058
操作系统中堆和栈的区别
https://blog.csdn.net/SpeedMe/article/details/22943191
java之jvm学习笔记十三(jvm基本结构)
https://blog.csdn.net/yfqnihao/article/details/8289363
JVM的内存区域划分
https://www.cnblogs.com/dolphin0520/p/3613043.html
操作系统之堆和栈的区别
https://www.cnblogs.com/George1994/p/6399895.html
JVM学习(2)——技术文章里常说的堆,栈,堆栈到底是什么,从os的角度总结
https://www.cnblogs.com/kubixuesheng/p/5202561.html
网友评论