一直不理解堆棧的概念蕾额,很多視頻課程講解又不全面但荤,學(xué)習(xí)代碼的數(shù)據(jù)結(jié)構(gòu)和算法成為了我最多困惑的地方罗岖。最近閑暇,查了下資料腹躁,基于Java語言整理下內(nèi)存區(qū)域劃分的基礎(chǔ)知識(shí)桑包。
由于Java程序是交由JVM執(zhí)行的,所以在談Java內(nèi)存區(qū)域劃分的時(shí)候事實(shí)上是指JVM內(nèi)存區(qū)域劃分纺非。在討論JVM內(nèi)存區(qū)域劃分之前哑了,先來看一下Java程序具體執(zhí)行的過程:
了解jvm的結(jié)構(gòu)之前,有必要先來了解一下操作系統(tǒng)的內(nèi)存基本結(jié)構(gòu)
以上是操作系統(tǒng)存儲(chǔ)層次【CPU <--- > 寄存器<--- > 緩存(最多三級(jí)緩存)<--- >內(nèi)存<--- >磁盤緩存<--- >固定磁盤存儲(chǔ)<--- >可移動(dòng)存儲(chǔ)介質(zhì)】的部分展示烧颖。
操作系統(tǒng)內(nèi)存布局:
操作系統(tǒng)內(nèi)存的堆棧:
棧區(qū)(stack):由編譯器自動(dòng)分配釋放 弱左,存放函數(shù)的參數(shù)值,局部變量的值等炕淮。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧拆火。
堆區(qū) (heap):一般由程序員分配釋放, 若程序員不釋放涂圆,程序結(jié)束時(shí)可能由OS回收 们镜。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表
棧是為執(zhí)行線程留出的內(nèi)存空間(當(dāng)線程創(chuàng)建的時(shí)候润歉,操作系統(tǒng)會(huì)為每個(gè)系統(tǒng)級(jí)的線程分配棧)模狭;
棧頂會(huì)為局部變量和數(shù)據(jù)預(yù)留塊,當(dāng)函數(shù)執(zhí)行完畢卡辰,塊就沒有用了胞皱,可能在下次的函數(shù)調(diào)用的時(shí)候再被使用邪意,棧通常都是采用后進(jìn)先出的方式預(yù)留空間九妈;因此最近的保留塊通常最先被釋放,從棧中釋放塊不過是指針的偏移(這里和數(shù)據(jù)結(jié)構(gòu)中的棧的意思類似)
堆的數(shù)據(jù)結(jié)構(gòu)并不是由系統(tǒng)支持的雾鬼,而是由函數(shù)庫提供的基本的malloc/realloc/free函數(shù)維護(hù)了一套內(nèi)部的堆數(shù)據(jù)結(jié)構(gòu)(所以才需要程序員自己釋放內(nèi)存萌朱,否則會(huì)造成內(nèi)存泄漏);
堆包含了一個(gè)鏈表來維護(hù)已用和空閑的內(nèi)存塊策菜;
申請(qǐng)內(nèi)存:
當(dāng)程序需要在堆中分配內(nèi)存的時(shí)候晶疼,會(huì)從內(nèi)部堆中尋找可用的內(nèi)存空間酒贬,通過鏈表找到符合大小的內(nèi)存塊(鏈表遍歷的方向是由低地址指向高地址),但是由于堆是不連續(xù)的內(nèi)存區(qū)域翠霍,當(dāng)找不到合適的內(nèi)存區(qū)域的時(shí)候锭吨,則會(huì)利用系統(tǒng)調(diào)用來動(dòng)態(tài)增加程序數(shù)據(jù)段的內(nèi)存大小寒匙;
釋放內(nèi)存:
當(dāng)系統(tǒng)受到程序的釋放內(nèi)存的申請(qǐng)的時(shí)候零如,會(huì)遍歷該鏈表,尋找第一個(gè)空間大于所申請(qǐng)的堆結(jié)點(diǎn)锄弱,然后該結(jié)點(diǎn)會(huì)從鏈表中刪除考蕾,并將該結(jié)點(diǎn)的空間釋放給內(nèi)存,這片內(nèi)存空間又會(huì)返回到堆結(jié)構(gòu)中会宪,會(huì)經(jīng)過內(nèi)存塊的組合肖卧,以便適合下次內(nèi)存分配申請(qǐng);(這里面如果沒有管理內(nèi)存分配在釋放內(nèi)存時(shí)很容易會(huì)造成內(nèi)存碎片)
為什么jvm的內(nèi)存是分布在操作系統(tǒng)的堆中呢掸鹅??因?yàn)椴僮飨到y(tǒng)的棧是操作系統(tǒng)管理的巍沙,它隨時(shí)會(huì)被回收,所以如果jvm放在棧中赎瞎,那java的一個(gè)null對(duì)象就很難確定會(huì)被誰回收了,那gc的存在就一點(diǎn)意義都沒有了务甥,而要對(duì)棧做到自動(dòng)釋放也是jvm需要考慮的牡辽,所以放在堆中就最合適不過了。
JVM 的內(nèi)存主要分為3個(gè)分區(qū)
堆區(qū)(Heap)-- 只存對(duì)象(數(shù)組)本身(引用類型的數(shù)據(jù))敞临,不存基本類型和對(duì)象的引用态辛。JVM只有一個(gè)堆區(qū),這個(gè)“堆”是動(dòng)態(tài)內(nèi)存分配意義上的堆——用于管理動(dòng)態(tài)生命周期的內(nèi)存區(qū)域挺尿。JVM的堆被同一個(gè)JVM實(shí)例中的所有Java線程共享奏黑,它通常由某種自動(dòng)內(nèi)存管理機(jī)制所管理,這種機(jī)制通常叫做“垃圾回收”(garbage collection编矾,GC)熟史。JVM規(guī)范并不強(qiáng)制要求JVM實(shí)現(xiàn)采用哪種GC算法。
棧區(qū)(Stack)-- 棧中只保存基礎(chǔ)數(shù)據(jù)類型的對(duì)象和對(duì)象引用窄俏。每個(gè)線程一個(gè)棧區(qū)蹂匹,每個(gè)棧區(qū)中的數(shù)據(jù)都是私有的,其他棧不能訪問凹蜈。棧內(nèi)有幀(方法調(diào)用會(huì)生成棧幀)分三個(gè)部分:基本類型變量區(qū)限寞,執(zhí)行環(huán)境上下文忍啸,操作指令區(qū)。
方法區(qū) -- 又叫靜態(tài)區(qū)履植,跟堆一樣计雌,被所有線程共享。方法區(qū)包含所有的class和static變量玫霎。方法區(qū)包含的都是在整個(gè)程序中永遠(yuǎn)唯一的元素白粉。如:class,satic鼠渺。
細(xì)化到增加jvm內(nèi)部的處理和pc寄存器的配合時(shí)鸭巴,可見,無論是在虛擬機(jī)中還是在我們虛擬機(jī)所寄宿的操作系統(tǒng)中功能目的是一致的拦盹,計(jì)算機(jī)上的pc寄存器是計(jì)算機(jī)上的硬件鹃祖,本來就是屬于計(jì)算機(jī),計(jì)算機(jī)用pc寄存器來存放“偽指令”或地址普舆,而相對(duì)于虛擬機(jī)恬口,pc寄存器它表現(xiàn)為一塊內(nèi)存(一個(gè)字長,虛擬機(jī)要求字長最小為32位)沼侣,虛擬機(jī)的pc寄存器的功能也是存放偽指令祖能,更確切的說存放的是將要執(zhí)行指令的地址,它甚至可以是操作系統(tǒng)指令的本地地址蛾洛,當(dāng)虛擬機(jī)正在執(zhí)行的方法是一個(gè)本地方法的時(shí)候养铸,jvm的pc寄存器存儲(chǔ)的值是undefined,所以應(yīng)該很明確的知道钞螟,虛擬機(jī)的pc寄存器是用于存放下一條將要執(zhí)行的指令的地址(字節(jié)碼流)谎碍。
當(dāng)一個(gè)classLoder啟動(dòng)的時(shí)候,classLoader的生存地點(diǎn)在jvm中的堆拯啦,然后它會(huì)去主機(jī)硬盤上將A.class裝載到j(luò)vm的方法區(qū)褒链,方法區(qū)中的這個(gè)字節(jié)文件會(huì)被虛擬機(jī)拿來new A字節(jié)碼()笋敞,然后在堆內(nèi)存生成了一個(gè)A字節(jié)碼的對(duì)象夯巷,然后A字節(jié)碼這個(gè)內(nèi)存文件有兩個(gè)引用一個(gè)指向A的class對(duì)象,一個(gè)指向加載自己的classLoader喷兼,如下圖季惯。
方法區(qū)中的字節(jié)碼內(nèi)存塊勉抓,除了記錄一個(gè)class自己的class對(duì)象引用和一個(gè)加載自己的ClassLoader引用之外候学,還記錄了以下信息。
從上面的圖隐圾,不難發(fā)現(xiàn)暇藏,原來jvm的設(shè)計(jì)的模型其實(shí)就是操作系統(tǒng)的模型盐碱,基于操作系統(tǒng)的角度沪伙,jvm就是個(gè)java.exe/javaw.exe焰坪,也就是一個(gè)應(yīng)用,而基于class文件來說儒恋,jvm就是個(gè)操作系統(tǒng)诫尽,而jvm的方法區(qū)炬守,也就相當(dāng)于操作系統(tǒng)的硬盤區(qū),所以他也叫permanent區(qū)曹洽,因?yàn)檫@個(gè)單詞是永久的意思辽剧,也就是永久區(qū),我們的磁盤就是不斷電的永久區(qū)偷崩。而java棧和操作系統(tǒng)棧是一致的阐斜,無論是生長方向還是管理的方式诀紊,至于堆,雖然概念上一致目標(biāo)也一致到推,分配內(nèi)存的方式也一直(new,或者malloc等等)莉测,但是由于他們的管理方式不同唧喉,jvm是gc回收八孝,而操作系統(tǒng)是程序員手動(dòng)釋放,所以在算法上有很多的差異子姜。
堆棧的概念有2種楼入,通常除了內(nèi)存上面的概念嘉熊,還有數(shù)據(jù)結(jié)構(gòu)里面的概念,兩者不完全相同凫佛。簡單的概念區(qū)分如下:
棧(操作系統(tǒng)-內(nèi)存):由編譯器自動(dòng)分配釋放 ,存放函數(shù)的參數(shù)值晨炕,局部變量的值等厚满。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧碧磅, 他們通常都是被調(diào)用時(shí)處于存儲(chǔ)空間中鲸郊,調(diào)用完畢立即釋放
堆(操作系統(tǒng)-內(nèi)存): 一般由程序員分配釋放, 若程序員不釋放秆撮,程序結(jié)束時(shí)可能由OS回收,分配方式倒是類似于鏈表盗蟆。生命周期由虛擬機(jī)的垃圾回收算法來決定(并不是一旦成為孤兒對(duì)象就能被回收)喳资。所以調(diào)用這些對(duì)象的速度要相對(duì)來得低一些
堆(數(shù)據(jù)結(jié)構(gòu)):堆可以被看成是一棵樹腾供,如:堆排序
棧(數(shù)據(jù)結(jié)構(gòu)):一種后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)
參考資料:
JVM與操作系統(tǒng)
https://zhuanlan.zhihu.com/p/44401058
操作系統(tǒng)中堆和棧的區(qū)別
https://blog.csdn.net/SpeedMe/article/details/22943191
java之jvm學(xué)習(xí)筆記十三(jvm基本結(jié)構(gòu))
https://blog.csdn.net/yfqnihao/article/details/8289363
JVM的內(nèi)存區(qū)域劃分
https://www.cnblogs.com/dolphin0520/p/3613043.html
操作系統(tǒng)之堆和棧的區(qū)別
https://www.cnblogs.com/George1994/p/6399895.html
JVM學(xué)習(xí)(2)——技術(shù)文章里常說的堆伴鳖,棧,堆棧到底是什么搞疗,從os的角度總結(jié)
https://www.cnblogs.com/kubixuesheng/p/5202561.html