咱們從編譯原理講起,不一樣的開發環境、開發語言都會有不一樣的策略。通常來講,程序運行時有三種內存分配策略:靜態的、棧式的、堆式的程序員
靜態存儲
是指在編譯時就可以肯定每一個數據目標在運行時的存儲空間需求,於是在編譯時就能夠給它們分配固定的內存空間。
這種分配策略要求程序代碼中不容許有可變數據結構的存在,也不容許有嵌套或者遞歸的結構出現,由於它們都會致使編譯程序沒法計算準確的存儲空間。算法
棧式存儲
棧式存儲分配是動態存儲分配,是由一個相似於堆棧的運行棧來實現的,和靜態存儲的分配方式相反。
在棧式存儲方案中,程序對數據區的需求在編譯時是徹底未知的,只有到了運行的時候才能知道,可是規定在運行中進入一個程序模塊的時候,必須知道該程序模塊所須要的數據區的大小才能分配其內存。和咱們在數據結構中所熟知的棧同樣,棧式存儲分配按照先進後出的原則進行分配。編程
堆式存儲
堆式存儲分配專門負責在編譯時或運行時,沒法肯定存儲要求的數據結構的內存分配。
好比可變長度串和對象實例,堆由大片的可利用塊或空閒塊組成,堆中的內存能夠按照任意順序分配和釋放。數組
2.2 堆和棧的比較
上面的定義從編譯原理的教材中總結而來,除靜態存儲分配以外,都顯得很呆板和難以理解,下面撇開靜態存儲分配,集中比較堆和棧:
從堆和棧的功能和做用來通俗的比較,堆主要用來存放對象的,棧主要是用來執行程序的.而這種不一樣又主要是因爲堆和棧的特色決定的:
在編程中,例如C/C++中,全部的方法調用都是經過棧來進行的,全部的局部變量,形式參數都是從棧中分配內存空間的。實際上也不是什麼分配,只是從棧頂向上用就行,就好像工廠中的傳送帶(conveyor belt)同樣,Stack Pointer會自動指引你到放東西的位置,你所要作的只是把東西放下來就行.退出函數的時候,修改棧指針就能夠把棧中的內容銷燬.這樣的模式速度最快, 固然要用來運行程序了.須要注意的是,在分配的時候,好比爲一個即將要調用的程序模塊分配數據區時,應事先知道這個數據區的大小,也就說是雖然分配是在程序運行時進行的,可是分配的大小多少是肯定的,不變的,而這個"大小多少"是在編譯時肯定的,不是在運行時.
堆是應用程序在運行的時候請求操做系統分配給本身內存,因爲從操做系統管理的內存分配,因此在分配和銷燬時都要佔用時間,所以用堆的效率很是低.可是堆的優勢在於,編譯器沒必要知道要從堆裏分配多少存儲空間,也沒必要知道存儲的數據要在堆裏停留多長的時間,所以,用堆保存數據時會獲得更大的靈活性。事實上,面向對象的多態性,堆內存分配是必不可少的,由於多態變量所需的存儲空間只有在運行時建立了對象以後才能肯定.在C++中,要求建立一個對象時,只需用 new命令編制相關的代碼便可。執行這些代碼時,會在堆裏自動進行數據的保存.固然,爲達到這種靈活性,必然會付出必定的代價:在堆裏分配存儲空間時會花掉更長的時間!這也正是致使咱們剛纔所說的效率低的緣由,看來列寧同志說的好,人的優勢每每也是人的缺點,人的缺點每每也是人的優勢(暈~).
2.3 JVM中的堆和棧
JVM是基於堆棧的虛擬機.JVM爲每一個新建立的線程都分配一個堆棧.也就是說,對於一個Java程序來講,它的運行就是經過對堆棧的操做來完成的。堆棧以幀爲單位保存線程的狀態。JVM對堆棧只進行兩種操做:以幀爲單位的壓棧和出棧操做。
咱們知道,某個線程正在執行的方法稱爲此線程的當前方法.咱們可能不知道,當前方法使用的幀稱爲當前幀。當線程激活一個Java方法,JVM就會在線程的 Java堆棧裏新壓入一個幀。這個幀天然成爲了當前幀.在此方法執行期間,這個幀將用來保存參數,局部變量,中間計算過程和其餘數據.這個幀在這裏和編譯原理中的活動紀錄的概念是差很少的.
從Java的這種分配機制來看,堆棧又能夠這樣理解:堆棧(Stack)是操做系統在創建某個進程時或者線程(在支持多線程的操做系統中是線程)爲這個線程創建的存儲區域,該區域具備先進後出的特性。
每個Java應用都惟一對應一個JVM實例,每個實例惟一對應一個堆。應用程序在運行中所建立的全部類實例或數組都放在這個堆中,並由應用全部的線程共享.跟C/C++不一樣,Java中分配堆內存是自動初始化的。Java中全部對象的存儲空間都是在堆中分配的,可是這個對象的引用倒是在堆棧中分配,也就是說在創建一個對象時從兩個地方都分配內存,在堆中分配的內存實際創建這個對象,而在堆棧中分配的內存只是一個指向這個堆對象的指針(引用)而已。數據結構
2.4 GC的思考
Java爲何慢?JVM的存在固然是一個緣由,但有人說,在Java中,除了簡單類型(int,char等)的數據結構,其它都是在堆中分配內存(因此說Java的一切都是對象),這也是程序慢的緣由之一。
個人想法是(應該說表明TIJ的觀點),若是沒有Garbage Collector(GC),上面的說法就是成立的.堆不象棧是連續的空間,沒有辦法期望堆自己的內存分配可以象堆棧同樣擁有傳送帶般的速度,由於,誰會爲你整理龐大的堆空間,讓你幾乎沒有延遲的從堆中獲取新的空間呢?
這個時候,GC站出來解決問題.咱們都知道GC用來清除內存垃圾,爲堆騰出空間供程序使用,但GC同時也擔負了另一個重要的任務,就是要讓Java中堆的內存分配和其餘語言中堆棧的內存分配同樣快,由於速度的問題幾乎是衆口一詞的對Java的詬病.要達到這樣的目的,就必須使堆的分配也可以作到象傳送帶同樣,不用本身操心去找空閒空間.這樣,GC除了負責清除Garbage外,還要負責整理堆中的對象,把它們轉移到一個遠離Garbage的純淨空間中無間隔的排列起來,就象堆棧中同樣緊湊,這樣Heap Pointer就能夠方便的指向傳送帶的起始位置,或者說一個未使用的空間,爲下一個須要分配內存的對象"指引方向".所以能夠這樣說,垃圾收集影響了對象的建立速度,聽起來很怪,對不對?
那GC怎樣在堆中找到全部存活的對象呢?前面說了,在創建一個對象時,在堆中分配實際創建這個對象的內存,而在堆棧中分配一個指向這個堆對象的指針(引用),那麼只要在堆棧(也有可能在靜態存儲區)找到這個引用,就能夠跟蹤到全部存活的對象.找到以後,GC將它們從一個堆的塊中移到另一個堆的塊中,並將它們一個挨一個的排列起來,就象咱們上面說的那樣,模擬出了一個棧的結構,但又不是先進後出的分配,而是能夠任意分配的,在速度能夠保證的狀況下, Isn't it great?
可是,列寧同志說了,人的優勢每每也是人的缺點,人的缺點每每也是人的優勢(再暈~~).GC()的運行要佔用一個線程,這自己就是一個下降程序運行性能的缺陷,更況且這個線程還要在堆中把內存翻來覆去的折騰.不只如此,如上面所說,堆中存活的對象被搬移了位置,那麼全部對這些對象的引用都要從新賦值.這些開銷都會致使性能的下降.
此消彼長,GC()的優勢帶來的效益是否蓋過了它的缺點致使的損失,我也沒有太多的體會,Bruce Eckel 是Java的支持者,王婆賣瓜,話不能全信.我的總的感受是,Java仍是很慢,它的發展還須要時間.多線程
上面的體會是我看了TIJ.3rdEdition.Revision4.0中第四章以後得出的,內容和前面的有些不一樣.我沒有看過侯捷的中文版本,但我以爲,在關鍵問題上,原版的TIJ的確更值得一讀.因此和中文版配合起來學習是比較不錯的選擇.
我只能算一個Java的初學者,沒想到起了這麼個題目,卻受到這麼多人的關注,欣喜之餘,也決心盡力寫好下面的每一篇.不過這一篇完了,我就該準備赴美簽證了,若是成功,那就要等到8月27號CS的研究生院開學以後,纔有時間會開始研究下一章了,但願能夠多從原版中獲取一點經驗.函數
堆和棧的區別和聯繫性能
1、簡單的能夠理解爲:
heap:是由malloc之類函數分配的空間所在地。地址是由低向高增加的。
stack:是自動分配變量,以及函數調用的時候所使用的一些空間。地址是由高向低減小的。
2、堆和棧的理論知識
2.1申請方式
stack:
由系統自動分配。 例如,聲明在函數中一個局部變量 int b; 系統自動在棧中爲b開闢空間
heap:
須要程序員本身申請,並指明大小,在c中malloc函數
如p1 = (char *)malloc(10);
在C++中用new運算符
如p2 = (char *)malloc(10);
可是注意p一、p2自己是在棧中的。
2.2
申請後系統的響應
棧:只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,不然將報異常提示棧溢出。
堆:首先應該知道操做系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,
會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,而後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,因爲找到的堆結點的大小不必定正好等於申請的大小,系統會自動的將多餘的那部分從新放入空閒鏈表中。
2.3申請大小的限制
棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就肯定的常數),若是申請的空間超過棧的剩餘空間時,將提示overflow。所以,能從棧得到的空間較小。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是因爲系統是用鏈表來存儲的空閒內存地址的,天然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。因而可知,堆得到的空間比較靈活,也比較大。
2.4申請效率的比較:
棧由系統自動分配,速度較快。但程序員是沒法控制的。
堆是由new分配的內存,通常速度比較慢,並且容易產生內存碎片,不過用起來最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。可是速度, 也最靈活
2.5堆和棧中的存儲內容
棧: 在函數調用時,第一個進棧的是主函數中後的下一條指令(函數調用語句的下一條可執行語句)的地址,而後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,而後是函數中的局部變量。注意靜態變量是不入棧的。
當本次函數調用結束後,局部變量先出棧,而後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。
堆:通常是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。
2.6存取效率的比較
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在運行時刻賦值的;
而bbbbbbbbbbb是在編譯時就肯定的;
可是,在之後的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。
好比:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
對應的彙編代碼
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指edx中,在根據edx讀取字符,顯然慢了。
?
2.7小結:
堆和棧的區別能夠用以下的比喻來看出:
使用棧就象咱們去飯館裏吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,沒必要理會切菜、洗菜等準備工做和洗碗、刷鍋等掃尾工做,他的好處是快捷,可是自由度小。
使用堆就象是本身動手作喜歡吃的菜餚,比較麻煩,可是比較符合本身的口味,並且自由度大。
堆和棧的區別主要分:
操做系統方面的堆和棧,如上面說的那些,很少說了。
還有就是數據結構方面的堆和棧,這些都是不一樣的概念。這裏的堆實際上指的就是(知足堆性質的)優先隊列的一種數據結構,第1個元素有最高的優先權;棧實際上就是知足先進後出的性質的數學或數據結構。
雖然堆棧,堆棧的說法是連起來叫,可是他們仍是有很大區別的,連着叫只是因爲歷史的緣由而已。學習