java內存管理-堆棧內存

轉自:http://blog.csdn.net/yechaodechuntian/article/details/22696285

http://my.oschina.net/u/565575/blog/80285

內存泄漏的定義: 對象不再被應用程序使用,但是垃圾回收器卻不能移除它們,因爲它們正在被引用。

綜述:在函數中定義的一些基本類型的變量和對象的引用變量都在函數的棧內存 中分配 。當在一段代碼塊定義一個變量時,Java就在棧中爲這個變量分配內存空間,當超過變量的作用域後,Java會自動釋放掉爲該變量所分配的內存空間, 該內存空間可以立即被另作他用。 堆內存用來存放由 new創建的對象和數組。 在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。 棧有一個很重要的特殊性,就是存在棧中的數據可以共享。比較類裏面的數值是否相等時,用equals()方法;當 測試兩個包裝類的引用是否指向同一個對象時,用==, 


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

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

當在一段代碼塊定義一個變量時,Java就在棧中爲這個變量分配內存空間,當超過變量的作用域後,Java會自動釋放掉爲該變量所分配的內存空間, 該內存空間可以立即被另作他用。 
   堆內存用來存放由 new創建的對象和數組。 
   在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。 
   在堆中產生了一個數組或對象後,還可以在棧中定義一個特殊的變量,讓棧中這個變量的取值等於數組或對象在堆內存中的首地址,棧中的這個變量就成了數組或對 象的引用變量。 

引用變量就相當於是爲數組或對象起的一個名稱,以後就可以在程序中使用棧中的引用變量來訪問堆中的數組或對象。 ,Java自動管理棧和堆,程序員不能直接地設置棧或堆。 

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

棧的優勢是,存取速度比堆要快,僅次於寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類 型的變量(,int, short, long, byte, float, double, boolean, char)和對象句柄。 
      棧有一個很重要的特殊性,就是存在棧中的數據可以共享 。 假設我們同時定義: 
int a = 3; 
int b = 3; 
編譯器先處理int a = 3;首先它會在棧中創建一個變量爲a的引用,然後查找棧中是否有3這個值,如果沒找到,就將3存放進來,然後將a指向3。接着處理int b = 3;在創建完b的引用變量後,因爲在棧中已經有3這個值,便將b直接指向3。這樣,就出現了a與b同時均指向3的情況。這時,如果再令a=4;那麼編譯器 會重新搜索棧中是否有4值,如果沒有,則將4存放進來,並令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。要注意這 種數據的共享與兩個對象的引用同時指向一個對象的這種共享是不同的,因爲這種情況a的修改並不會影響到b, 它是由編譯器完成的,它有利於節省空間。而一個對象引用變量修改了這個對象的內部狀態,會影響到另一個對象引用變量。 

String是一 個特殊的包裝類數據。可以用: 
String str = new String("abc"); 
String str = "abc"; 
兩種的形式來創建,第一種是用new()來新 建對象的,它會在存放於堆中。每調用一次就會創建一個新的對象。 
而第二種是先在棧中創建一個對String類的對象引用變量str,然後查找棧 中有沒有存放"abc",如果沒有,則將"abc"存放進棧,並令str指向”abc”,如果已經有”abc” 則直接令str指向“abc”。

較類裏面的數值是否相等時,用equals()方法;當 測試兩個包裝類的引用是否指向同一個對象時,用== 下面用例子說明上面的理論。 
String str1 = "abc"; 
String str2 = "abc"; 
System.out.println(str1==str2); //true 
可以看出str1和 str2是指向同一個對象的。 

String str1 =new String ("abc"); 
String str2 =new String ("abc"); 
System.out.println(str1==str2); // false 
用new的方式是生成不同的對象。每一次生成一個 。 
   因此用第二種方式創建多個”abc”字符串,在內存中其實只存在一個對象而已. 這種寫法有利與節省內存空間. 同時它可以在一定程度上提高程序的運行速度,因爲JVM會自動根據棧中數據的實際情況來決定是否有必要創建新對象。而對於String str = new String("abc");的代碼,則一概在堆中創建新對象,而不管其字符串值是否相等,是否有必要創建新對象,從而加重了程序的負擔。 

另一方面, 要注意: 我們在使用諸如String str = "abc";的格式定義類時,總是想當然地認爲,創建了String類的對象str。擔心陷阱!對象可能並沒有被創建!而可能只是指向一個先前已經創建的 對象。只有通過new()方法才能保證每次都創建一個新的對象。由於String類的immutable性質,當String變量需要經常變換其值時,應 該考慮使用StringBuffer類,以提高程序效率。(因爲String 被設計成不可變(immutable)類,所以它的所有對象都是不可變對象。在這段代碼中,s 原先指向一個String 對象,內容是"Hello",然後我們對s 進行了+操作,那麼s 所指向的那個對象是否發生了改變呢?答案是沒有。這時,s 不指向原來那個對象了,而指向了另一個String 對象,內容爲"Hello world!",原來那個對象還存在於內存之中,只是s 這個引用變量不再指向它了。

java中內存分配策略及堆和棧的比較 
2.1 內存分配策略

靜態存儲分配要求在編譯時能知道所有變量的存儲要求,棧式存儲分配要求在過程的入口處必須知道所有的存儲要求,而堆式存儲分配則專門負責在編譯時或運行時模塊入口處都無法確定存儲要求的數據結構的內存分配,比如可變長度串和對象實例.堆由大片的可利用塊或空閒塊組成,堆中的內存可以按照任意 順序分配和釋放. 

2.3 JVM中的堆和棧

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

JVM運行時,將內存分爲堆和棧,堆中存放的是創建的對象,JAVA字符串對象內存實現時,在堆中開闢了一快很小的內存,叫字符串常量池,用來存放特定的字符串對象。 

關於String對象的創建,兩種方式是不同的,第一種不用new的簡單語法,即 
String s1="JAVA"; 
創建步驟是先看常量池中有沒有與"JAVA"相同的的字符串對象,如果有,將s1指向該對象,若沒有,則創建一個新對象, 並讓s1指向它。 
第二種是new語法 
String s2="JAVA"; 
這種語法是在堆而不是在常量池中創建對象,並將 s2指向它,然後去字符串常量池中看看,是否有與之相同的內容的對象,如果有,則將new出來的字符串對象與字符串常量池中的對象聯繫起來,如果沒有,則 在字符串常量池中再創建一個包含該內容的字符串對象,並將堆內存中的對象與字符串常量池中新建出來的對象聯繫起來。 
這就是字符串的一次投入,終 生回報的內存機制,對字符串的比較帶來好處。

通俗版

1. 容易被搞暈的--堆和棧 
由於"堆"和"棧"這兩個概念是看不見摸不着的東西,讓很多程序員都整不明白是怎麼回
事,其實這兩個概念也沒有什麼好研究的,因爲堆和棧程序員根本沒有辦法控制其具體內容。
我們只需要瞭解一點,棧與堆都是Java 用來在內存中存放數據的地方就行了。然後再
弄清楚這兩個概念分別對應這程序開發的什麼操作,以及堆和棧的區別即可。
1.1 堆--用new 建立,垃圾自動回收負責回收 
1、堆是一個"運行時"數據區,類實例化的對象就是從堆上去分配空間的;
2、在堆上分配空間是通過"new"等指令建立的;
3、Java 針對堆的操作和C++的區別就是,Java 不需要在空間不用的時候來顯式的釋放;
4、Java 的堆是由Java 的垃圾回收機制來負責處理的,堆是動態分配內存大小,垃圾
收集器可以自動回收不再使用的內存空間。
5、但缺點是,因爲在運行時動態分配內存,所以內存的存取速度較慢。
例如:
String s1 = "asdf";
String s2 = "asdf";
System.out.println(s1==s2);
String s1 =new String ("asdf");
String s2 =new String ("asdf");
System.out.println(s1==s2);
就是在堆上開闢的空間來存放String 的對象。
1.2 棧--存放基本數據類型,速度快 
1、棧中主要存放一些基本類型的變量(int, short, long, byte, float, double,
boolean, char)和對象句柄;
2、棧的存取速度比堆要快;
3、棧數據可以共享;
4、棧的數據大小與生存期必須是確定的,缺乏靈活性。
例如:
就是在堆上開闢的空間來存放String 的對象。
1.3 何謂棧的"數據共享" 
棧其中一個特性就是"數據共享",那麼什麼是"數據共享"呢?
我們這裏面所說的數據共享,並不是由程序員來控制的,而是JVM 來控制的,指的是是
系統自動處理的方式。
比如定義兩個變量:
這兩個變量所指向的棧上的空間地址是同一個,這就是所謂的"數據共享"。
它的工作方式是這樣的:
JVM 處理int a = 5,首先在棧上創建一個變量爲a 的引用,然後去查找棧上是否還有5
這個值,如果沒有找到,那麼就將5存放進來,然後將a 指向5。
接着處理int b = 5,在創建完b 的引用後,因爲在棧中已經有5這個值,便將b 直接
指向5。
String str = new String("abc");
int a = 3;
int a = 5;
int b = 5;
於是,就出現了a 與b 同時指向5的內存地址的情況。
1.4 實例化對象的兩種方法 
對於String 這個類來說它可以用兩種方法進行建立:

用這兩個形式創建的對象是不同的,第一種是用new()來創建對象的,它是在堆上開闢
空間,每調用一次都會在堆上創建一個新的對象。
而第二種的創建方法則是先在棧上創建一個String 類的對象引用,然後再去查找棧中
有沒有存放"asdf",如果沒有,則將"asdf"存放進棧,並讓str 指向"asdf",如果已經有
"asdf" 則直接把str 指向"abc"。
我們在比較兩個String 是否相等時,一定是用"equals()"方法,而當測試兩個包裝類
的引用是否指向同一個對象時,我們應該用"= ="。
因此,我們可以通過"= ="判斷是否相等來驗證棧上面的數據共享的問題。
例1:
該程序的運行結果是,"true",那麼這說明"s1"和"s2"都是指向同一個對象的。
例2:
該程序的運行結果是,"false",這說明用new 的方式是生成的對象,每個對象都指向不同
的地方。


1.堆棧都是一種數據項按序排列的數據結構,只能在一端(稱爲棧頂(top))對數據項進行插入和刪除。要點:堆,順序隨意。棧,後進先出(Last-In/First-Out)。

2.堆和棧的區別可以用如下的比喻來看出:使用棧就象我們去飯館裏喫飯,只管點菜(發出申請)、付錢、和喫(使用),喫飽了就走,不必理會切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。使用堆就象是自己動手做喜歡喫的菜餚,比較麻煩,但是比較符合自己的口味,而且自由度大。

3.在java中方法裏定義的變量,存儲在棧內存,當方法結束時,自動銷燬。而new的對象是存儲在堆內存裏的,不會隨方法結束而銷燬,除非沒有被另外的引用變量引用,會在被JVM在適當的時候回收。

在 JAVA 中,有六個不同的地方可以存儲數據:
 
1. 寄存器( register )。這是最快的存儲區,因爲它位於不同於其他存儲區的地方——處理器內部。但是寄存器的數量極其有限,所以寄存器由編譯器根據需求進行分配。你不能直接控制,也不能在程序中感覺到寄存器存在的任何跡象。 

2. 堆棧( stack )。位於通用 RAM 中,但通過它的“堆棧指針”可以從處理器哪裏獲得支持。堆棧指針若向下移動,則分配新的內存;若向上移動,則釋放那些內存。這是一種快速有效的分配存儲方法,僅次於寄存器。創建程序時候, JAVA 編譯器必須知道存儲在堆棧內所有數據的確切大小和生命週期,因爲它必鬚生成相應的代碼,以便上下移動堆棧指針。這一約束限制了程序的靈活性,所以雖然某些 JAVA 數據存儲在堆棧中——特別是對象引用,但是 JAVA 對象不存儲其中。 

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

4. 靜態存儲( static storage )。這裏的“靜態”是指“在固定的位置”(儘管也位於RAM)。靜態存儲裏存放程序運行時一直存在的數據。你可用關鍵字 static 來標識一個對象的特定元素是靜態的,但 JAVA 對象本身從來不會存放在靜態存儲空間裏。 

5. 常量存儲( constant storage )。常量值通常直接存放在程序代碼內部,這樣做是安全的,因爲它們永遠不會被改變。有時,在嵌入式系統中,常量本身會和其他部分分割離開,所以在這種情況下,可以選擇將其放在 ROM 中。(這點的一個例子便是字符串池,所有字面字符串和字符串常量表達式都被自動intered從而被放到一個特殊的靜態存儲空間裏。)


(還需要了解堆結構 堆通常是一個可以被看做一棵樹的數組對象。堆總是滿足下列性質:

  • 堆中某個節點的值總是不大於或不小於其父節點的值;
  • 堆總是一棵完全樹。)

附上String知識:

1.String是最基本的數據類型嗎?
基本數據類型包括byte、int、char、long、float、double、boolean 和short。
java.lang.String 類是final 類型的,因此不可以繼承這個類、不能修改這個類。爲了提高效率節省空間,我們應該用StringBuffer 類

2.String s = "Hello";s = s + " world!";這兩行代碼執行後,原始的 String
對象中的內容到底變了沒有?沒有。因爲String 被設計成不可變(immutable)類,所以它的所有對象都是不可變對象。在
這段代碼中,s 原先指向一個String 對象,內容是"Hello",然後我們對s 進行了+操作,那麼s 所指向的那個對象是否發生了改變呢?答案是沒有。這時,s 不指向原來那個對象了,而指向了另一個String 對象,內容爲"Hello world!",原來那個對象還存在於內存之中,只是s 這個引用變量不再指向它了。通過上面的說明,我們很容易導出另一個結論,如果經常對字符串進行各種各樣的修改,或者說,不可預見的修改,那麼使用String 來代表字符串的話會引起很大的內存開銷。因爲String 對象建立之後不能再改變,所以對於每一個不同的字符串,都需要一個String 對象來表示。這時,應該考慮使用StringBuffer 類,它允許修改,而不是每個不同的字符串都要生成一個新的對象。並且,這兩種類的對象轉換十分容易。

3.數組有沒有 length()這個方法? String有沒有 length()這個方法?
數組沒有length()這個方法,有length 的屬性。String 有有length()這個方法。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章