操作系統分為棧和堆,棧由操作系統管理,會(huì )有操作系統進(jìn)行自動(dòng)回收,堆由用戶(hù)進(jìn)行分配使用
我們需要注意的是操作系統的棧與堆并不是數據結構里的那個(gè)棧與堆
這是個(gè)人筆記,所以添加了操作系統內存中的棧與堆內容(高手請直接跳過(guò)這部分內容)
一般情況下程序會(huì )被存放在Rom(只讀內存器,比如硬盤(pán))或Flash(閃存)中,運行時(shí)需要拷到RAM(隨機存儲器)中進(jìn)行執行,RAM會(huì )分別存儲不同的信息,如下圖所示:
內存中的棧區處于相對較高的地址以地址的增長(cháng)方向為上的話(huà),棧地址是向下增長(cháng)的。
棧中分配局部變量空間,堆區是向上增長(cháng)的用于分配程序員申請的內存空間。
另外還有靜態(tài)區是分配靜態(tài)變量,全局變量空間的;
只讀區是分配常量和程序代碼空間的。
下面讓我們一起看這一段的代碼:
int a = 0; //全局初始化區char *p1; //全局未初始化區main(){int b; //棧char s[] = "abc"; //棧char *p2; //棧char *p3 = "123456"; //123456\0在常量區,p3在棧上。static int c =0; //全局(靜態(tài))初始化區p1 = (char *)malloc(10); //堆p2 = (char *)malloc(20); //堆}從中進(jìn)行分析:
(1)申請方式和回收方式不同
我們很容易就能注意到了:堆和棧的第一個(gè)區別就是申請方式不同,棧(英文名稱(chēng)是stack)是系統自動(dòng)分配空間的,例如我們定義一個(gè) char a,系統會(huì )自動(dòng)在棧上為其開(kāi)辟空間。而堆(英文名稱(chēng)是heap)則是程序員根據需要自己申請的空間,例如malloc(10),開(kāi)辟十個(gè)字節的空間。
由于棧上的空間是自動(dòng)分配自動(dòng)回收的,所以棧上的數據的生存周期只是在函數的運行過(guò)程中,運行后就釋放掉,不可以再訪(fǎng)問(wèn)。而堆上的數據只要程序員不釋放空間,就一直可以訪(fǎng)問(wèn)到,不過(guò)缺點(diǎn)是一旦忘記釋放會(huì )造成內存泄露。
(2)申請后系統的響應:
棧:只要棧的剩余空間大于所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。
堆:首先應該知道操作系統有一個(gè)記錄空閑內存地址的鏈表,當系統收到程序的申請時(shí),會(huì )遍歷該鏈表,尋找第一個(gè)空間大于所申請空間的堆結點(diǎn),然后將該結點(diǎn)從空閑結點(diǎn)鏈表中刪除,并將該結點(diǎn)的空間分配給程序。還有,對于大多數系統,會(huì )在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的 delete語(yǔ)句(C++中的)才能正確的釋放本內存空間。另外,由于找到的堆結點(diǎn)的大小不一定正好等于申請的大小,系統會(huì )自動(dòng)的將多余的那部分重新放入空閑鏈表中。 也就是說(shuō)堆會(huì )在申請后還要做一些后續的工作,這就會(huì )引出申請效率的問(wèn)題。
(3)申請效率的比較
棧:由系統自動(dòng)分配,速度較快。但程序員是無(wú)法控制的。
堆:是由new分配的內存,一般速度比較慢,而且容易產(chǎn)生內存碎片,不過(guò)用起來(lái)最方便。
(4).申請大小的限制
棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話(huà)的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說(shuō)是1M,總之是一個(gè)編譯時(shí)就確定的常數),如果申請的空間超過(guò)棧的剩余空間時(shí),將提示overflow。因此,能從棧獲得的空間較小。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來(lái)存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的虛擬內存。由此可見(jiàn),堆獲得的空間比較靈活,也比較大。
(5)堆和棧中的存儲內容
對于C/C++來(lái)說(shuō)
棧: 在函數調用時(shí),第一個(gè)進(jìn)棧的是主函數中函數調用后的下一條指令(函數調用語(yǔ)句的下一條可執行語(yǔ)句)的地址,然后是函數的各個(gè)參數,在大多數的C編譯器中,參數是由右往左入棧的,然后是函數中的局部變量。注意靜態(tài)變量是不入棧的。
當本次函數調用結束后,局部變量先出棧,然后是參數,最后棧頂指針指向最開(kāi)始存的地址,也就是主函數中的下一條指令,程序由該點(diǎn)繼續運行。
堆:一般是在堆的頭部用一個(gè)字節存放堆的大小。堆中的具體內容有程序員安排。
首先先介紹操作系統內存與JVM內存的聯(lián)系與區別:
1.操作系統分為棧和堆,棧由操作系統管理,會(huì )有操作系統進(jìn)行自動(dòng)回收,堆由用戶(hù)進(jìn)行分配使用
2.JVM內存使用的操作系統的堆,以防JVM分配的內存被操作系統回收
3.JVM本地方法棧指的是操作系統的棧
4.操作系統的PC寄存器,是計算機上的存儲硬件,與內存條一樣的硬件,但是寄存區位于CPU內,被稱(chēng)為Cache,用于加快數據訪(fǎng)問(wèn)速度。內存是外掛在CPU的數據總線(xiàn)上的
5.JVM PC寄存器位于操作系統的堆中
下面為圖示:

為了便于理解添加圖示:

1- JVM PC寄存器
PC寄存器配合字節碼解釋器,選取下一條字節碼指令來(lái)解釋執行,線(xiàn)程私有,每個(gè)線(xiàn)程都有一個(gè)PC寄存器。為了確保切換線(xiàn)程后能恢復到原來(lái)進(jìn)程正確的執行位置。如果執行的不是Java方法,而是本地方法Native Method,這個(gè)計數器值為空(Undefined),如果是Java方法則保存的是正在執行的虛擬機字節碼指令的地址,可以看做是當前線(xiàn)程所執行的字節碼的行號指示器,會(huì )自增取下一條字節碼指令的地址。這是JVM 規范的唯一沒(méi)有OutMemoryError的內存區域。
2- Java 虛擬機棧
當啟動(dòng)一個(gè)線(xiàn)程時(shí),JVM就會(huì )給這個(gè)線(xiàn)程分配一個(gè)棧,所以棧的生命周期是和線(xiàn)程一樣的。
Java方法執行的內存模型:每個(gè)Java方法的執行都會(huì )創(chuàng )建一個(gè)棧幀(Stack Frame),方法的調用到執行完畢(正常退出,或者異常退出)對應著(zhù)一個(gè)棧幀的入棧和出棧的過(guò)程
一個(gè)棧幀包括:局部變量表、操作數棧、動(dòng)態(tài)鏈接、方法出口等。
棧幀結構:
1)局部變量表:存放的是編譯期間可知的基本數據類(lèi)型,和對象的引用,比如方法的參數變量,還有方法內的局部變量;需要注意的是如果這個(gè)方法是普通的方法,那么還會(huì )有自己的本身對象的一個(gè)引用(this,索引為0),如果是靜態(tài)方法就沒(méi)有這個(gè)自身對象的引用。一個(gè)單位局部變量空間為32位,64位長(cháng)度的long和double數據類(lèi)型會(huì )占用兩個(gè)局部變量空間。變量通過(guò)聲明的順序的索引來(lái)進(jìn)行訪(fǎng)問(wèn)的,
2)操作數棧(Operand Stack):是一個(gè)存儲中間變量結果的棧結構,只能通過(guò)入棧和出棧來(lái)進(jìn)行訪(fǎng)問(wèn)。Java虛擬機指令是主要是通過(guò)操作數棧來(lái)獲取操作數(Operand)的,而不是寄存器。
舉例:
int a = 100; int b = 98;// iload_0,iload_1,iadd,istore_2//iload指令是指將局部變量表中一個(gè)int變量加載到操作棧中//iadd指令彈出操作數棧的兩個(gè)變量,進(jìn)行加法運算,然后將結果壓入棧中//istore指令是指將操作棧一個(gè)數值存儲到局部變量表中int c = a+b;

3)動(dòng)態(tài)鏈接(暫時(shí)略)
4)方法出口(暫時(shí)略)
5)通常程序員所說(shuō)的Java中的棧指的就是虛擬機的局部變量表。
6)兩種異常:StackOverflowError(??臻g溢出),當線(xiàn)程請求的棧深度大于虛擬機所允許的深度,將會(huì )拋出此異常;OutMemoryError(??臻g拓展內存溢出),當虛擬機棧支持動(dòng)態(tài)拓展時(shí),如果在擴展時(shí)無(wú)法申請到足夠的內存時(shí),就會(huì )拋出此異常。
3- 本地方法棧(Native Method)
1)本地方法棧和虛擬機棧的作用是一樣的,只是服務(wù)的對象不一樣,虛擬機方法棧是為虛擬機執行java方法(字節碼)服務(wù)的,而本地方法棧是為虛擬機執行Native方法服務(wù)的。
2)Sun HotSpot 直接將本地方法棧和虛擬機棧 合二為一。
3)兩種異常:和虛擬機棧一樣
JVM規范中的內存空間
4- Java堆(Java Heap)
1)所有線(xiàn)程共享的內存區域,與虛擬機同生命周期。
2)主要任務(wù)是存儲對象實(shí)例,基本上所有對象實(shí)例和數組都在Heap上分配空間,但是也不這么絕對,因為編譯器優(yōu)化。
3)Java堆是一塊很大的內存區域,為了加速GC回收的效率,把這個(gè)堆有按照不同粒度進(jìn)行細分
5- 方法區(Method Area)
1)所有線(xiàn)程共享的內存區域,與虛擬機同生命周期。
2)存儲已被虛擬機加載的類(lèi)信息、常量、靜態(tài)變量、編譯后的代碼等
3)按照代劃分,方法區在HotSpot中被劃分為永久代(Permanent Generation)
4)不需要物理連續的存儲空間,可拓展(通過(guò) -XX:MaxPermSize,-XX:MinPermSize)
5)永久代并不永久,GC會(huì )對常量池的回收,以及類(lèi)型的卸載。
6)異常:OutMemoryError,當方法區無(wú)法滿(mǎn)足內存分配時(shí)。
7)運行時(shí)常量池(Runtime Constant Pool)
7.1是方法區的一部分,Class文件中除了有版本、字段、方法、接口等描述信息,還會(huì )有一項信息是常量池,用于存放編譯期間生成的各種字面量和符號引用以及翻譯后的直接引用,這部分內容將在類(lèi)加載后存放在方法區的運行時(shí)常量池中。
7.2運行時(shí)常量池中的常量,不一定來(lái)源于一開(kāi)始加載的Class文件(編譯期間產(chǎn)生常量),也可以在運行時(shí)將新的常量放入常量池中,這是運行時(shí)常量池動(dòng)態(tài)性的體現。
補充:
Java中的內存分配:
棧:存儲局部變量
堆:存儲new出來(lái)的數組或對象
方法區:類(lèi)的方法代碼,變量名,方法名,訪(fǎng)問(wèn)權限,返回值等等
本地方法區:和系統相關(guān)
寄存器:給CPU使用
Java虛擬機內存模型中定義的訪(fǎng)問(wèn)操作和物理計算機處理的基本一致,如圖:

Java中通過(guò)多線(xiàn)程機制使得多個(gè)任務(wù)同時(shí)執行處理,所有的線(xiàn)程共享JVM內存區域,而每一個(gè)線(xiàn)程又單獨有自己的工作內存,當線(xiàn)程與內存區域進(jìn)行交互時(shí),數據從主存拷貝到工作內存,進(jìn)而交由線(xiàn)程處理(操作碼+操作數)
因水平有限可能存在錯誤,而且博文的邏輯上似乎有點(diǎn)混亂,先將就一下看吧,后面進(jìn)行調整
聯(lián)系客服