------- android培訓、java培訓、期待與您交流! ---------
Java語言把內存分為棧內存和堆內存兩種。
基本的數據類型變量和對象的引用變量(也就是說對象在堆內存中的地址)是存儲在棧內存中的。其中數組也是對象。當在程序中定義一個變量,JVM就會為這個變量分配一塊內存,一旦超過了變量的作用域,JVM就會自動釋放掉為該變量分配的內存地址。這就是java語言的垃圾回收機制。
堆內存用于存放由new創建的對象和數組。在堆中分配的內存,也是由JVM來管理。在堆中產生了一個數組或者對象后,還可以在棧中定義一個特殊的變量,這個變量的取值等于數組或者對象在堆內存中的首地址。
1 car a=new car();
car是一個類,new car()之后就產生了一個對象。a就是存儲這個對象的在內存的首地址。
在棧中的這個特殊的變量就變成了數組或者對象的引用變量,以后就可以在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量相當于為數組或者對象起的一個別名,或者代號。
引用變量是普通變量,定義時在棧中分配內存。引用變量在程序運行到作用域外釋放。
而數組和對象本身在堆中分配,即使程序運行到使用new產生數組和對象的語句所在地代碼塊之外,數組和對象本身占用的堆內存也不會被釋放。
數組和對象在沒有引用變量指向它的時候,才變成垃圾,不能再被使用,但是仍然占著內存,在隨后的一個不確定的時間被Java垃圾回收機制給釋放掉。因此,java程序比較占內存。
實際上,棧中的變量指向堆內存的變量,是java這門語言的指針。
棧式存儲分配要求在過程的入口處必須知道所有的存儲要求,而堆式存儲分配則專門負責在編譯時或運行時模塊入口處都無法確定存儲要求的數據結構的內存分配,比如可變長度串和對象實例.堆由大片的可利用塊或空閑塊組成,堆中的內存可以按照任意順序分配和釋放.。
從堆和棧的功能和作用來通俗的比較,堆主要用來存放對象的,棧主要是用來執行程序的。
下面用一個例子來說明:
Person p = new Person();創建一個對象都在內存中做了什么事情?1:先將硬盤上指定位置的Person.class文件加載進內存。2:執行main方法時,在棧內存中開辟了main方法的空間(壓棧-進棧),然后在main方法的棧區分配了一個變量p。3:在堆內存中開辟一個實體空間,分配了一個內存首地址值。new4:在該實體空間中進行屬性的空間分配,并進行了默認初始化。5:對空間中的屬性進行顯示初始化。6:進行實體的構造代碼塊初始化。7:調用該實體對應的構造函數,進行構造函數初始化。()8:將首地址賦值給p ,p變量就引用了該實體。(指向了該對象)
p就是實例化Person類對象的指針,如果僅僅是new Person()就創建了一個匿名對象。如果你僅僅只是對Person類對象的方法調用一次,這個時候就可以創建該類的匿名對象。失去了指針,會在不確定的時候被JVM給清除掉。
從這里會引申 成員變量和局部變量的區別。
成員變量和局部變量的區別:1:成員變量直接定義在類中。 局部變量定義在方法中,參數上,語句中。2:成員變量在這個類中有效。局部變量只在自己所屬的大括號內有效,大括號結束,局部變量失去作用域。3:成員變量存在于堆內存中,隨著對象的產生而存在,消失而消失。局部變量存在于棧內存中,隨著所屬區域的運行而存在,結束而釋放。
這個可以看出,成員變量是在堆內存中的,隨著對象的產生而存在,消失而消失。方法中的局部變量存在于棧內存中,方法運行完了局部變量也就出棧了。程序進入CPU運行的是棧內存的數據。
接下來通過代碼來演示下:
代碼段A:
1 String str1 =new String ("abc");2 3 String str2 =new String ("abc");4 5 System.out.PRintln(str1==str2); // false
代碼段B:
1 String str3 = "abc";2 3 String str4 = "abc";4 5 System.out.println(str3==str4); //true
代碼段C:
1 String str5 = new String("abc");2 3 String str6 = "abc";4 5 System.out.println(str5==str6); // false
String是一個特殊的封裝類,存儲的是字符串。
String str = "abc"創建對象的過程:【1】 首先在常量池中查找是否存在內容為"abc"字符串對象;【2】 如果不存在則在常量池中創建"abc",并讓str引用該對象;【3】如果存在則直接讓str引用該對象。
"abc"保存在哪里呢?常量池在堆中還是在棧中?
常量池屬于類信息的一部分,而類信息反映到JVM內存模型中是對應存在于JVM內存模型的方法區。也就是說這個類信息 中的常量池概念是存在于在方法區中,而方法區是在JVM內存模型中的堆中由JVM來分配的,所以"abc"可以說存在于堆中(而有些資料,為了把方法區的 堆區別于JVM的堆,把方法區稱為棧)。一般這種情況下,"abc"在編譯時就被寫入字節碼中,所以class被加載時,JVM就為"abc"在常量池中 分配內存,所以和靜態區差不多。 引
String str = new String("abc")創建實例的過程:【1】 首先在堆中(不是常量池)創建一個指定的對象"abc",并讓str引用指向該對象;【2】 在字符串常量池中查看,是否存在內容為"abc"字符串對象 【3】若存在,則將new出來的字符串對象與字符串常量池中的對象聯系起來【4】若不存在,則在字符串常量池中創建一個內容為"abc"的字符串對象,并將堆中的對象與之聯系起來。
因此在代碼段B中,str3和str4變量儲存的實際是棧中"abc"的內存地址,地址一樣所以str3和str4的值一樣。所以輸出為true。同樣的,在代碼段A中,每new一次,"abc"字符串存儲的位置在堆內存中都不一樣,因此str1和str3的值不一樣。代碼段C中的str5和str6的值也不會一樣。
如果要比較代碼段A中str1和str2對象存儲的字符串內容是不是一樣,應該調用equals()方法,例如:
1 package blog; 2 3 public class StringDemo { 4 5 public static void main(String[] args) { 6 String str1 = new String("abc"); 7 String str2 = "abc"; 8 9 System.out.println(str1.equals(str2)); // ture10 11 }12 13 }
intern 方法可以返回該字符串在常量池中的對象的引用。下面是intern在API上的解釋:
intern
public String intern()返回字符串對象的規范化表示形式。 一個初始為空的字符串池,它由類 String 私有地維護。 當調用 intern 方法時,如果池已經包含一個等于此 String 對象的字符串(用 equals(Object) 方法確定),則返回池中的字符串。否則,將此 String 對象添加到池中,并返回此 String 對象的引用。 它遵循以下規則:對于任意兩個字符串 s 和 t,當且僅當 s.equals(t) 為 true 時,s.intern() == t.intern() 才為 true。 所有字面值字符串和字符串賦值常量表達式都使用 intern 方法進行操作。字符串字面值在 Java Language Specification 的 §3.10.5 定義。 返回:一個字符串,內容與此字符串相同,但一定取自具有唯一字符串的池。可以通過下面代碼來測試一下new出來的和直接賦值的區別。
1 package blog; 2 3 public class StringDemo { 4 5 public static void main(String[] args) { 6 7 String str1 = "abc"; 8 9 String str2 = new String("abc").intern(); 10 11 System.out.println(str1==str2); //true12 13 } 14 15 }
再補充一個小示例:
1 String str1 = "a"; 2 String str2 = "bc"; 3 String str3 = "a"+"bc"; 4 String str4 = str1+str2; 5 6 System.out.println(str3==str4); //false7 str4 = (str1+str2).intern(); 8 System.out.println(str3==str4); //true
因為上面的str4是存入的兩個變量,是不會放在常量池中的,但是調用intern方法之后,就會去常量池中找有沒有"abc", 找到了就會返回其內存地址。
新聞熱點
疑難解答