棧與堆講解--(面試題)

棧和堆的區(qū)別是什么? 為什么說棧的速度快,堆的速度慢?(為什么棧的儲存分配比堆存儲分配快)

一、問題

1、為什么棧的儲存分配比堆存儲分配快?

原因

1)、棧中的數據占內存大小在編譯時是確定的,比如一個int類型就占4B,所以變量地址好計算,所以分配和銷毀和訪問速度都比較快.
堆中的數據占內存大小一般在編譯時是不確定的,在運行時才能知道大小,所以其地址只有在運行時計算,而且運行時可能占內存大小還有變動,所以對這樣的數據的分配,銷毀和訪問都非常不方便,速度也慢一些.


2)、棧里放的是地址,堆里可以放數據也可以放地址(想象下堆里的東西也有可能指向別的地方)
每個地址都會指向給定的數據,不然就沒有存在的必要了,同樣的道理,堆中的數據沒有被指針指向的話,也沒有存在的必要了,所以當obj=null時就釋放內存了。
Java有個好處就是沒有指針,Java中的傳遞的都是傳引用,不像c++還能傳地址,比如指針p++和p+1兩個的結果完全不同。


3)、棧是編譯時分配空間,而堆是動態(tài)分配(運行時分配空間),所以棧的速度快
cpu有專門的寄存器(esp,ebp)來操作棧,堆都是使用間接尋址的。??禳c。


4)、java 的堆是一個運行時數據區(qū),類的(對象從中分配空間。這些對象通過new、newarray等指令建立,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優(yōu)勢是可以動態(tài)地分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態(tài)分配內存的,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由于要在運行時動態(tài)分配內存,存取速度較慢。
棧的優(yōu)勢是,存取速度比堆要快,僅次于寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變量(,int, short, long, byte, float, double, boolean, char)和對象句柄。

基本數據類型存儲在“?!敝?,對象引用類型實際存儲在“堆”中,在棧中只是保留了引用內存的地址值。

2、為什么棧數據可以共享?

棧的優(yōu)勢是,存取速度比堆要快,僅次于寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變量(,int, short, long, byte, float, double, boolean, char)和對象句柄。

棧有一個很重要的特殊性,就是存在棧中的數據可以共享。假設我們同時定義:

int a = 3;

int b = 3;

編譯器先處理int a = 3;首先它會在棧中創(chuàng)建一個變量為a的引用,然后查找棧中是否有3這個值,如果沒找到,就將3存放進來,然后將a指向3。接著處理int b = 3;在創(chuàng)建完b的引用變量后,因為在棧中已經有3這個值,便將b直接指向3。這樣,就出現(xiàn)了a與b同時均指向3的情況。這時,如果再令a=4;那么編譯器會重新搜索棧中是否有4值,如果沒有,則將4存放進來,并令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。要注意這種數據的共享與兩個對象的引用同時指向一個對象的這種共享是不同的,因為這種情況a的修改并不會影響到b, 它是由編譯器完成的,它有利于節(jié)省空間。而一個對象引用變量修改了這個對象的內部狀態(tài),會影響到另一個對象引用變量

3、棧有什么特點?

棧,有個特點,先進去的后出來,里邊放的是進程中正在執(zhí)行的方法,
比如說方法

public void A(){
B();
}
public void B(){
C();
}
public void C(){

}
假設一個程序只調用A()方法,(開始時棧是空的):

程序運行的時候,先執(zhí)行A()方法,因為A方法調用了B()方法,這時因為A()沒有執(zhí)行完,中斷A()方法,將其放入棧中暫存,(此時因為棧原來是空的,A方法被push進棧后,位于棧底),執(zhí)行B()方法,執(zhí)行時因為B方法調用了C()方法,此時中斷B方法的執(zhí)行,將B()方法push入棧,由于棧中已經有A方法,B被push進去后,位于A的上面;C()方法執(zhí)行完后,去棧中查找位于棧頂的方法,查到是方法B,方法B繼續(xù)執(zhí)行,執(zhí)行完后再去棧中查找位于棧頂的方法,此時棧內只又一個方法,那就是方法A,方法A有繼續(xù)執(zhí)行,A執(zhí)行完后,棧中已經沒有方法,程序運行結束。

假設下邊這個柱狀的東西是棧

| |
|B()|---位于??谧罱姆椒ㄏ缺籶op出來執(zhí)行完成
|A()|---棧底的方法最后執(zhí)行完
———

二、棧與堆原理


·
1、堆內存,java的堆是java虛擬機所管理的內存中最大的一塊。這個內存的唯一目的就是存放對象實例。幾乎所有的對象實例都在這里分配內存(注意不是所有?。?。所以java堆是被所有線程所共享的一塊內存。在java虛擬機的規(guī)范中,java堆可以處于物理上不連續(xù)的內存空間,只要邏輯上是連續(xù)的就好。和我們的電腦磁盤空間是一樣的。如果在堆中沒有內存可供使用做實例分配,并且堆也沒有擴展的時候,就會出現(xiàn)OutOfMemoryError異常。
·


·
1、java 的棧內存。它的真正名稱叫做:java虛擬機棧。它和java堆內存的有一個很大的區(qū)別在于,它是線程私有的。它的生命周期也和線程的生命周期捆綁在一起。 每個java方法在執(zhí)行的時候都會創(chuàng)建一個棧幀用來存儲局部變量表,操作數棧,動態(tài)鏈接,方法出口等信息。每一個方法從調用直至完成執(zhí)行后的過程,就對應著一個棧幀在虛擬機棧中的入棧到出棧的過程。在java虛擬機棧中,局部變量表所需要的內存空間在編譯期間完成分配的。也就是說在進入一個方法時,這個方法在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。那如果這個方法的線程所需要的內存大于這個空間呢?結果是會拋出OutOfMemoryError異常。另外如果線程請求的棧深度大于虛擬機所設的深度,將拋出 StackOverflowError異常。

三、堆和棧的比較

1、 JVM中的堆和棧

JVM是基于堆棧的虛擬機.JVM為每個新創(chuàng)建的線程都分配一個堆棧.也就是說,對于一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態(tài)。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。

我們知道,某個線程正在執(zhí)行的方法稱為此線程的當前方法.我們可能不知道,當前方法使用的幀稱為當前幀。當線程激活一個Java方法,JVM就會在線程的 Java堆棧里新壓入一個幀。這個幀自然成為了當前幀.在此方法執(zhí)行期間,這個幀將用來保存參數,局部變量,中間計算過程和其他數據.這個幀在這里和編譯原理中的活動紀錄的概念是差不多的.

從Java的這種分配機制來看,堆棧又可以這樣理解:堆棧(Stack)是操作系統(tǒng)在建立某個進程時或者線程(在支持多線程的操作系統(tǒng)中是線程)為這個線程建立的存儲區(qū)域,該區(qū)域具有先進后出的特性。

每一個Java應用都唯一對應一個JVM實例,每一個實例唯一對應一個堆。應用程序在運行中所創(chuàng)建的所有類實例或數組都放在這個堆中,并由應用所有的線程共享.跟C/C++不同,Java中分配堆內存是自動初始化的。Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在堆棧中分配,也就是說在建立一個對象時從兩個地方都分配內存,在堆中分配的內存實際建立這個對象,而在堆棧中分配的內存只是一個指向這個堆對象的指針(引用)而已。

2、Java 中的堆和棧

Java把內存劃分成兩種:一種是棧內存,一種是堆內存。

在函數中定義的一些基本類型的變量和對象的引用變量都在函數的棧內存中分配。

當在一段代碼塊定義一個變量時,Java就在棧中為這個變量分配內存空間,當超過變量的作用域后,Java會自動釋放掉為該變量所分配的內存空間,該內存空間可以立即被另作他用。

堆內存用來存放由new創(chuàng)建的對象和數組。

在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。

在堆中產生了一個數組或對象后,還可以在棧中定義一個特殊的變量,讓棧中這個變量的取值等于數組或對象在堆內存中的首地址,棧中的這個變量就成了數組或對象的引用變量。

引用變量就相當于是為數組或對象起的一個名稱,以后就可以在程序中使用棧中的引用變量來訪問堆中的數組或對象。

具體的說:

棧與堆都是Java用來在Ram中存放數據的地方。與C++不同,Java自動管理棧和堆,程序員不能直接地設置棧或堆。

Java的堆是一個運行時數據區(qū),類的(對象從中分配空間。這些對象通過new、newarray、anewarray和multianewarray等指令建立,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優(yōu)勢是可以動態(tài)地分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態(tài)分配內存的,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由于要在運行時動態(tài)分配內存,存取速度較慢。

棧的優(yōu)勢是,存取速度比堆要快,僅次于寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變量(,int, short, long, byte, float, double, boolean, char)和對象句柄。

棧有一個很重要的特殊性,就是存在棧中的數據可以共享。假設我們同時定義:

int a = 3;

int b = 3;

編譯器先處理int a = 3;首先它會在棧中創(chuàng)建一個變量為a的引用,然后查找棧中是否有3這個值,如果沒找到,就將3存放進來,然后將a指向3。接著處理int b = 3;在創(chuàng)建完b的引用變量后,因為在棧中已經有3這個值,便將b直接指向3。這樣,就出現(xiàn)了a與b同時均指向3的情況。這時,如果再令a=4;那么編譯器會重新搜索棧中是否有4值,如果沒有,則將4存放進來,并令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。要注意這種數據的共享與兩個對象的引用同時指向一個對象的這種共享是不同的,因為這種情況a的修改并不會影響到b, 它是由編譯器完成的,它有利于節(jié)省空間。而一個對象引用變量修改了這個對象的內部狀態(tài),會影響到另一個對象引用變量

四、Java把內存分成兩種


一種叫做棧內存,一種叫做堆內存
1、棧內存

在函數中定義的一些基本類型的變量和對象的引用變量都是在函數的棧內存中分配。當在一段代碼塊中定義一個變量時,java就在棧中為這個變量分配內存空間,當超過變量的作用域后,java會自動釋放掉為該變量分配的內存空間,該內存空間可以立刻被另作他用。

2、堆內存

堆內存用于存放由new創(chuàng)建的對象和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。在堆中產生了一個數組或者對象后,還可以在棧中定義一個特殊的變量,這個變量的取值等于數組或者對象在堆內存中的首地址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,以后就可以在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量相當于為數組或者對象起的一個別名,或者代號。

綜合

·
引用變量是普通變量,定義時在棧中分配內存,引用變量在程序運行到作用域外釋放。而數組&對象本身在堆中分配,即使程序運行到使用new產生數組和對象的語句所在地代碼塊之外,數組和對象本身占用的堆內存也不會被釋放,數組和對象在沒有引用變量指向它的時候,才變成垃圾,不能再被使用,但是仍然占著內存,在隨后的一個不確定的時間被垃圾回收器釋放掉。這個也是java比較占內存的主要原因,實際上,棧中的變量指向堆內存中的變量,這就是 Java 中的指針!

五、java中內存分配策略


按照編譯原理的觀點,程序運行時的內存分配有三種策略,分別是靜態(tài)的,棧式的,和堆式的.

1、靜態(tài)存儲分配

是指在編譯時就能確定每個數據目標在運行時刻的存儲空間需求,因而在編譯時就可以給他們分配固定的內存空間.這種分配策略要求程序代碼中不允許有可變數據結構(比如可變數組)的存在,也不允許有嵌套或者遞歸的結構出現(xiàn),因為它們都會導致編譯程序無法計算準確的存儲空間需求.

2、棧式存儲分配

也可稱為動態(tài)存儲分配,是由一個類似于堆棧的運行棧來實現(xiàn)的.和靜態(tài)存儲分配相反,在棧式存儲方案中,程序對數據區(qū)的需求在編譯時是完全未知的,只有到運行的時候才能夠知道,但是規(guī)定在運行中進入一個程序模塊時,必須知道該程序模塊所需的數據區(qū)大小才能夠為其分配內存.和我們在數據結構所熟知的棧一樣,棧式存儲分配按照先進后出的原則進行分配。

3、堆式存儲分配

則專門負責在編譯時或運行時模塊入口處都無法確定存儲要求的數據結構的內存分配,比如可變長度串和對象實例.堆由大片的可利用塊或空閑塊組成,堆中的內存可以按照任意順序分配和釋放.

六、在JAVA中,有六個不同的地方可以存儲數據:


1. 寄存器(register)。

這是最快的存儲區(qū),因為它位于不同于其他存儲區(qū)的地方——處理器內部。但是寄存器的數量極其有限,所以寄存器由編譯器根據需求進行分配。你不能直接控制,也不能在程序中感覺到寄存器存在的任何跡象。
------最快的存儲區(qū), 由編譯器根據需求進行分配,我們在程序中無法控制.

2. 堆棧(stack)。

位于通用RAM中,但通過它的“堆棧指針”可以從處理器哪里獲得支持。堆棧指針若向下移動,則分配新的內存;若向上移動,則釋放那些 內存。這是一種快速有效的分配存儲方法,僅次于寄存器。創(chuàng)建程序時候,JAVA編譯器必須知道存儲在堆棧內所有數據的確切大小和生命周期,因為它必須生成 相應的代碼,以便上下移動堆棧指針。這一約束限制了程序的靈活性,所以雖然某些JAVA數據存儲在堆棧中——特別是對象引用,但是JAVA對象不存儲其 中。
------存放基本類型的變量數據和對象,數組的引用,但對象本身不存放在棧中,而是存放在堆(new 出來的對象)或者常量池中(字符串常量對象存放在常量池中)

3. 堆(heap)。

一種通用性的內存池(也存在于RAM中),用于存放所以的JAVA對象。堆不同于堆棧的好處是:編譯器不需要知道要從堆里分配多少存儲區(qū) 域,也不必知道存儲的數據在堆里存活多長時間。因此,在堆里分配存儲有很大的靈活性。當你需要創(chuàng)建一個對象的時候,只需要new寫一行簡單的代碼,當執(zhí)行 這行代碼時,會自動在堆里進行存儲分配。當然,為這種靈活性必須要付出相應的代碼。用堆進行存儲分配比用堆棧進行存儲存儲需要更多的時間。
------存放所有new出來的對象。

4. 靜態(tài)存儲(static storage)。

這里的“靜態(tài)”是指“在固定的位置”。靜態(tài)存儲里存放程序運行時一直存在的數據。你可用關鍵字static來標識一個對象的特定元素是靜態(tài)的,但JAVA對象本身從來不會存放在靜態(tài)存儲空間里。
------存放靜態(tài)成員(static定義的)

5. 常量存儲(constant storage)。

常量值通常直接存放在程序代碼內部,這樣做是安全的,因為它們永遠不會被改變。有時,在嵌入式系統(tǒng)中,常量本身會和其他部分分割離開,所以在這種情況下,可以選擇將其放在ROM中
------存放字符串常量和基本類型常量(public static final)

6. 非RAM存儲。

·
如果數據完全存活于程序之外,那么它可以不受程序的任何控制,在程序沒有運行時也可以存在。


七、堆與棧存儲數據(Java 中詳情請看六)


對于棧和常量池中的對象可以共享,對于堆中的對象不可以共享。
棧中的數據大小和生命周期是可以確定的,當沒有引用指向數據時,這個數據就會消失。堆中的對象的由垃圾回收器負責回收,因此大小和生命周期不需要確定,具有很大的靈活性。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,621評論 1 32
  • 所有知識點已整理成app app下載地址 J2EE 部分: 1.Switch能否用string做參數? 在 Jav...
    侯蛋蛋_閱讀 2,700評論 1 4
  • 前言 不知道大家有沒有這樣一種感覺,程序員的數量井噴了??赡苁且驗榛ヂ?lián)網火了,也可能是各家培訓機構為我們拉來了大量...
    活這么大就沒飽過閱讀 2,832評論 6 25
  • 引言:看了網上一些作品,沒有特別清晰的一個結構,所以,這里本人整理一下Java的堆棧相關知識。Java 中的堆和棧...
    androidjp閱讀 13,554評論 18 189
  • 這篇文章是我之前翻閱了不少的書籍以及從網絡上收集的一些資料的整理,因此不免有一些不準確的地方,同時不同JDK版本的...
    高廣超閱讀 16,040評論 3 83

友情鏈接更多精彩內容