java中的堆,棧和方法區(轉)


來源:https://www.cnblogs.com/iliuyuet/p/5603618.html

https://blog.csdn.net/lin542405822/article/details/80338256 

https://www.cnblogs.com/yangmengdx3/p/4690953.html

關於java中堆棧的存儲,先要說一下java的數據類型:

基本類型: 共有8種,即int, short, long, byte, float, double, boolean, char。這種類型的定義是通過諸如int a = 3; long b = 255L;的形式來定義的,稱為自動變量。值得注意的是,自動變量存的是字面值,不是類的實例,即不是類的引用,這里並沒有類的存在。如int a = 3; 這里的a是一個指向int類型的引用,指向3這個字面值。這些字面值的數據,由於大小可知,生存期可知(這些字面值固定定義在某個程序塊里面,程序塊退出 后,字段值就消失了),出於追求速度的原因,就存在於棧中。棧的存取速度要比堆快每個線程都有自己獨立的棧,當方法執行完畢,棧里面的內存空間就會自動被回收,而堆在整個JVM中只有一個(所以堆中的數據可被多個線程共享),堆里面的內存空間由GC來負責回收。

 

包裝數據類型:如Integer, String, Double等將相應的基本數據類型包裝起來的類。這些類數據全部存在於堆中,Java用new()語句來顯示地告訴編譯器,在運行時才根據需要動態創 建,因此比較靈活,但缺點是要占用更多的時間。

注意:String是一個特殊的包裝類數據。即可以用String str = new String("abc");的形式來創建,也可以用String str = "abc";的形式來創建。前者是規范的類的創建過程,即在Java中,一切都是對象,而對象是類的實例,全部通過new()的形式來創建。Java 中的有些類,如DateFormat類,可以通過該類的getInstance()方法來返回一個新創建的類,似乎違反了此原則。其實不然。該類運用了單 例模式來返回類的實例,只不過這個實例是在該類內部通過new()來創建的,而getInstance()向外部隱藏了此細節。

 

下面舉些例子來說明:

關於String str = "abc"的內部工作。Java內部將此語句轉化為以下幾個步驟: 

(1)先定義一個名為str的對String類的對象引用變量:String str; 

(2)在棧中查找有沒有存放值為"abc"的地址,如果沒有,則開辟一個存放字面值為"abc"的地址,接着創建一個新的String類的對象o,並 將o的字符串值指向這個地址,而且在棧中這個地址旁邊記下這個引用的對象o。如果已經有了值為"abc"的地址,則查找對象o,並返回o的地址。 

(3)將str指向對象o的地址。 

 *********************************************************************************************************

為了更好地說明這個問題,我們可以通過以下的幾個代碼進行驗證。 


String str1 = "abc"; 
String str2 = "abc"; 
System.out.println(str1==str2); //true 

注意,我們這里並不用str1.equals(str2);的方式,因為這將比較兩個字符串的值是否相等。==號,根據JDK的說明,只有在兩個引用都指向了同一個對象時才返回真值(也就是棧中對象的應用相同!!!)。而我們在這里要看的是,str1與str2是否都指向了同一個對象。 
事實上:JVM在棧中創建了兩個引用str1和str2,但在堆中只創建了一個對象,而且兩個引用都指向了這個對象。 

我們再來更進一步,將以上代碼改成: 

String str1 = "abc"; 
String str2 = "abc"; 
str1 = "bcd"; 
System.out.println(str1 + "," + str2); //bcd, abc 
System.out.println(str1==str2); //false 

這就是說,賦值的變化導致了類對象引用的變化,str1指向了另外一個新對象!而str2仍舊指向原來的對象。上例中,當我們將str1的值改為"bcd"時,JVM發現在棧中沒有存放該值的地址,便開辟了這個地址,並創建了一個新的對象,其字符串的值指向這個地址。 

 

我們再接着看以下的代碼。 

String str1 = new String("abc"); 
String str2 = "abc"; 
System.out.println(str1==str2); //false 

創建了兩個引用。創建了兩個對象。兩個引用分別指向不同的兩個對象。 

事實上,String類被設計成為不可改變(immutable)的類。如果你要改變其值,可以,但JVM在運行時根據新值悄悄創建了一個新對象,然 后將這個對象的地址返回給原來類的引用。這個創建過程雖說是完全自動進行的,但它畢竟占用了更多的時間。在對時間要求比較敏感的環境中,會帶有一定的不良 影響。 

 

 

我們寫代碼時的注意點:

(1)我們在使用諸如String str = "abc";的格式定義類時,總是想當然地認為,我們創建了String類的對象str。擔心陷阱!對象可能並沒有被創建!唯一可以肯定的是,指向 String類的引用被創建了。至於這個引用到底是否指向了一個新的對象,必須根據上下文來考慮,除非你通過new()方法來顯要地創建一個新的對象。因 此,更為准確的說法是,我們創建了一個指向String類的對象的引用變量str,這個對象引用變量指向了某個值為"abc"的String類。清醒地認 識到這一點對排除程序中難以發現的bug是很有幫助的。

(2)使用String str = "abc";的方式,可以在一定程度上提高程序的運行速度,因為JVM會自動根據棧中數據的實際情況來決定是否有必要創建新對象。而對於String str = new String("abc");的代碼,則一概在堆中創建新對象,而不管其字符串值是否相等,是否有必要創建新對象,從而加重了程序的負擔。這個思想應該是 享元模式的思想,但JDK的內部在這里實現是否應用了這個模式,不得而知。 

(3)當比較包裝類里面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用==。 

(4)由於String類的immutable性質,當String變量需要經常變換其值時,應該考慮使用StringBuffer類,以提高程序效率。 

 

堆棧中數據的創建和清理

1.  基本數據類型、對象的引用,局部變量都是存放在棧內存中的。

    它的生命期是跟隨線程的生命期,線程結束棧內存也就釋放,對於棧來說不存在垃圾回收問題,只要線程一結束該棧就Over,棧被線程私有。

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

    棧內存中的數據,沒有默認初始化值,需要手動設置。
2. new創建的實例化對象及數組,是存放在堆內存中的,當堆內存中的數據不再被棧引用時,就會被gc回收。

 這塊區域也是線程共享的。

   堆內存中所有的實體都有內存地址值,並且都有默認的初始值。

 

3.方法區是被所有線程共享,所有字段和方法字節碼,以及一些特殊方法如構造函數,接口代碼也在此定義。簡單說,所有定義的方法的信息都保存在該區域,此區域屬於共享區間。靜態變量    +常量+類信息+運行時常量池存在方法區中,實例變量存在堆內存中。
  又叫靜態區,跟堆一樣,被所有的線程共享。方法區包含所有的class和static變量。 
  方法區中包含的都是在整個程序中永遠唯一的元素,如class,static變量。 

 

 

例:

public   class  AppMain                //運行時, jvm 把appmain的信息都放入方法區    
{    
  public   static   void  main(String[] args)  //main 方法本身放入方法區。    
  {    
    Sample test1 = new  Sample( " 測試1 " );   //test1是引用,所以放到棧區里, Sample是自定義對象應該放到堆里面    
    Sample test2 = new  Sample( " 測試2 " );    
  
    test1.printName();    
    test2.printName();    
  }    
} 
public class Sample //運行時, jvm 把appmain的信息都放入方法區 { /** 范例名稱 */   private name; //new Sample實例后, name 引用放入棧區里, name 對象放入堆里 /** 構造方法 */   public Sample(String name)   {      this .name = name;   } /** 輸出 */   public void printName() //print方法本身放入 方法區里。   {     System.out.println(name);   } }

下面是一些從其他的地方找的能加深理解的例子(https://www.cnblogs.com/ibelieve618/p/6380328.html

 

例1:

 

main()
  int x=1;
show ()
  int x=2

主函數main()中定義變量int x=1,show()函數中定義變量int x=1。最后show()函數執行完畢。


以上程序執行步驟:

第1步——main()函數是程序入口,JVM先執行,在棧內存中開辟一個空間,存放int類型變量x,同時附值1。
第2步——JVM執行show()函數,在棧內存中又開辟一個新的空間,存放int類型變量x,同時附值2。
     此時main空間與show空間並存,同時運行,互不影響。
第3步——show()執行完畢,變量x立即釋放,空間消失。但是main()函數空間仍存在,main中的變量x仍然存在,不受影響。

 

例2

main()
  int[] x=new int[3];
  x[0]=20
  x=null;

以上步驟執行步驟
第1、2步——與示例2完全一樣,略。

第3步——執行x=null;
  null表示空值,即x的引用數組內存地址0x0045被刪除了,則不再指向堆中的數組。此時,堆中的數組不再被x使用了,即被視為垃圾,JVM會啟動垃圾回收機制,不定時自動刪除。

例3:

Car c=new Car;
c.num=5;
Car c1=c;
c1.color="green";
c.run();

Car c1=c,這句話相當於將對象復制一份出來,兩個對象的內存地址值一樣。所以指向同一個實體,對c1的屬性修改,相當於c的屬性也改了。

。。。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM