操作系統(tǒng)分為棧和堆,棧由操作系統(tǒng)管理,會有操作系統(tǒng)進行自動回收,堆由用戶進行分配使用
我們需要注意的是操作系統(tǒng)的棧與堆并不是數(shù)據(jù)結構里的那個棧與堆
這是個人筆記,所以添加了操作系統(tǒng)內存中的棧與堆內容(高手請直接跳過這部分內容)
一般情況下程序會被存放在Rom(只讀內存器,比如硬盤)或Flash(閃存)中,運行時需要拷到RAM(隨機存儲器)中進行執(zhí)行,RAM會分別存儲不同的信息,如下圖所示:
內存中的棧區(qū)處于相對較高的地址以地址的增長方向為上的話,棧地址是向下增長的。
棧中分配局部變量空間,堆區(qū)是向上增長的用于分配程序員申請的內存空間。
另外還有靜態(tài)區(qū)是分配靜態(tài)變量,全局變量空間的;
只讀區(qū)是分配常量和程序代碼空間的。
下面讓我們一起看這一段的代碼:
int a = 0; //全局初始化區(qū)char *p1; //全局未初始化區(qū)main(){int b; //棧char s[] = "abc"; //棧char *p2; //棧char *p3 = "123456"; //123456\0在常量區(qū),p3在棧上。static int c =0; //全局(靜態(tài))初始化區(qū)p1 = (char *)malloc(10); //堆p2 = (char *)malloc(20); //堆}
從中進行分析:
(1)申請方式和回收方式不同
我們很容易就能注意到了:堆和棧的第一個區(qū)別就是申請方式不同,棧(英文名稱是stack)是系統(tǒng)自動分配空間的,例如我們定義一個 char a,系統(tǒng)會自動在棧上為其開辟空間。而堆(英文名稱是heap)則是程序員根據(jù)需要自己申請的空間,例如malloc(10),開辟十個字節(jié)的空間。
由于棧上的空間是自動分配自動回收的,所以棧上的數(shù)據(jù)的生存周期只是在函數(shù)的運行過程中,運行后就釋放掉,不可以再訪問。而堆上的數(shù)據(jù)只要程序員不釋放空間,就一直可以訪問到,不過缺點是一旦忘記釋放會造成內存泄露。
(2)申請后系統(tǒng)的響應:
棧:只要棧的剩余空間大于所申請空間,系統(tǒng)將為程序提供內存,否則將報異常提示棧溢出。
堆:首先應該知道操作系統(tǒng)有一個記錄空閑內存地址的鏈表,當系統(tǒng)收到程序的申請時,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序。還有,對于大多數(shù)系統(tǒng),會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的 delete語句(C++中的)才能正確的釋放本內存空間。另外,由于找到的堆結點的大小不一定正好等于申請的大小,系統(tǒng)會自動的將多余的那部分重新放入空閑鏈表中。 也就是說堆會在申請后還要做一些后續(xù)的工作,這就會引出申請效率的問題。
(3)申請效率的比較
棧:由系統(tǒng)自動分配,速度較快。但程序員是無法控制的。
堆:是由new分配的內存,一般速度比較慢,而且容易產(chǎn)生內存碎片,不過用起來最方便。
(4).申請大小的限制
棧:在Windows下,棧是向低地址擴展的數(shù)據(jù)結構,是一塊連續(xù)的內存的區(qū)域。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預先規(guī)定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數(shù)),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。
堆:堆是向高地址擴展的數(shù)據(jù)結構,是不連續(xù)的內存區(qū)域。這是由于系統(tǒng)是用鏈表來存儲的空閑內存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統(tǒng)中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。
(5)堆和棧中的存儲內容
對于C/C++來說
棧: 在函數(shù)調用時,第一個進棧的是主函數(shù)中函數(shù)調用后的下一條指令(函數(shù)調用語句的下一條可執(zhí)行語句)的地址,然后是函數(shù)的各個參數(shù),在大多數(shù)的C編譯器中,參數(shù)是由右往左入棧的,然后是函數(shù)中的局部變量。注意靜態(tài)變量是不入棧的。
當本次函數(shù)調用結束后,局部變量先出棧,然后是參數(shù),最后棧頂指針指向最開始存的地址,也就是主函數(shù)中的下一條指令,程序由該點繼續(xù)運行。
堆:一般是在堆的頭部用一個字節(jié)存放堆的大小。堆中的具體內容有程序員安排。
首先先介紹操作系統(tǒng)內存與JVM內存的聯(lián)系與區(qū)別:
1.操作系統(tǒng)分為棧和堆,棧由操作系統(tǒng)管理,會有操作系統(tǒng)進行自動回收,堆由用戶進行分配使用
2.JVM內存使用的操作系統(tǒng)的堆,以防JVM分配的內存被操作系統(tǒng)回收
3.JVM本地方法棧指的是操作系統(tǒng)的棧
4.操作系統(tǒng)的PC寄存器,是計算機上的存儲硬件,與內存條一樣的硬件,但是寄存區(qū)位于CPU內,被稱為Cache,用于加快數(shù)據(jù)訪問速度。內存是外掛在CPU的數(shù)據(jù)總線上的
5.JVM PC寄存器位于操作系統(tǒng)的堆中
下面為圖示:
為了便于理解添加圖示:
1- JVM PC寄存器
PC寄存器配合字節(jié)碼解釋器,選取下一條字節(jié)碼指令來解釋執(zhí)行,線程私有,每個線程都有一個PC寄存器。為了確保切換線程后能恢復到原來進程正確的執(zhí)行位置。如果執(zhí)行的不是Java方法,而是本地方法Native Method,這個計數(shù)器值為空(Undefined),如果是Java方法則保存的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址,可以看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器,會自增取下一條字節(jié)碼指令的地址。這是JVM 規(guī)范的唯一沒有OutMemoryError的內存區(qū)域。
2- Java 虛擬機棧
當啟動一個線程時,JVM就會給這個線程分配一個棧,所以棧的生命周期是和線程一樣的。
Java方法執(zhí)行的內存模型:每個Java方法的執(zhí)行都會創(chuàng)建一個棧幀(Stack Frame),方法的調用到執(zhí)行完畢(正常退出,或者異常退出)對應著一個棧幀的入棧和出棧的過程
一個棧幀包括:局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等。
棧幀結構:
1)局部變量表:存放的是編譯期間可知的基本數(shù)據(jù)類型,和對象的引用,比如方法的參數(shù)變量,還有方法內的局部變量;需要注意的是如果這個方法是普通的方法,那么還會有自己的本身對象的一個引用(this,索引為0),如果是靜態(tài)方法就沒有這個自身對象的引用。一個單位局部變量空間為32位,64位長度的long和double數(shù)據(jù)類型會占用兩個局部變量空間。變量通過聲明的順序的索引來進行訪問的,
2)操作數(shù)棧(Operand Stack):是一個存儲中間變量結果的棧結構,只能通過入棧和出棧來進行訪問。Java虛擬機指令是主要是通過操作數(shù)棧來獲取操作數(shù)(Operand)的,而不是寄存器。
舉例:
int a = 100; int b = 98;// iload_0,iload_1,iadd,istore_2//iload指令是指將局部變量表中一個int變量加載到操作棧中//iadd指令彈出操作數(shù)棧的兩個變量,進行加法運算,然后將結果壓入棧中//istore指令是指將操作棧一個數(shù)值存儲到局部變量表中int c = a+b;
3)動態(tài)鏈接(暫時略)
4)方法出口(暫時略)
5)通常程序員所說的Java中的棧指的就是虛擬機的局部變量表。
6)兩種異常:StackOverflowError(??臻g溢出),當線程請求的棧深度大于虛擬機所允許的深度,將會拋出此異常;OutMemoryError(??臻g拓展內存溢出),當虛擬機棧支持動態(tài)拓展時,如果在擴展時無法申請到足夠的內存時,就會拋出此異常。
3- 本地方法棧(Native Method)
1)本地方法棧和虛擬機棧的作用是一樣的,只是服務的對象不一樣,虛擬機方法棧是為虛擬機執(zhí)行java方法(字節(jié)碼)服務的,而本地方法棧是為虛擬機執(zhí)行Native方法服務的。
2)Sun HotSpot 直接將本地方法棧和虛擬機棧 合二為一。
3)兩種異常:和虛擬機棧一樣
JVM規(guī)范中的內存空間
4- Java堆(Java Heap)
1)所有線程共享的內存區(qū)域,與虛擬機同生命周期。
2)主要任務是存儲對象實例,基本上所有對象實例和數(shù)組都在Heap上分配空間,但是也不這么絕對,因為編譯器優(yōu)化。
3)Java堆是一塊很大的內存區(qū)域,為了加速GC回收的效率,把這個堆有按照不同粒度進行細分
5- 方法區(qū)(Method Area)
1)所有線程共享的內存區(qū)域,與虛擬機同生命周期。
2)存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、編譯后的代碼等
3)按照代劃分,方法區(qū)在HotSpot中被劃分為永久代(Permanent Generation)
4)不需要物理連續(xù)的存儲空間,可拓展(通過 -XX:MaxPermSize,-XX:MinPermSize)
5)永久代并不永久,GC會對常量池的回收,以及類型的卸載。
6)異常:OutMemoryError,當方法區(qū)無法滿足內存分配時。
7)運行時常量池(Runtime Constant Pool)
7.1是方法區(qū)的一部分,Class文件中除了有版本、字段、方法、接口等描述信息,還會有一項信息是常量池,用于存放編譯期間生成的各種字面量和符號引用以及翻譯后的直接引用,這部分內容將在類加載后存放在方法區(qū)的運行時常量池中。
7.2運行時常量池中的常量,不一定來源于一開始加載的Class文件(編譯期間產(chǎn)生常量),也可以在運行時將新的常量放入常量池中,這是運行時常量池動態(tài)性的體現(xiàn)。
補充:
Java中的內存分配:
棧:存儲局部變量
堆:存儲new出來的數(shù)組或對象
方法區(qū):類的方法代碼,變量名,方法名,訪問權限,返回值等等
本地方法區(qū):和系統(tǒng)相關
寄存器:給CPU使用
Java虛擬機內存模型中定義的訪問操作和物理計算機處理的基本一致,如圖:
Java中通過多線程機制使得多個任務同時執(zhí)行處理,所有的線程共享JVM內存區(qū)域,而每一個線程又單獨有自己的工作內存,當線程與內存區(qū)域進行交互時,數(shù)據(jù)從主存拷貝到工作內存,進而交由線程處理(操作碼+操作數(shù))
因水平有限可能存在錯誤,而且博文的邏輯上似乎有點混亂,先將就一下看吧,后面進行調整