JAVA對象引用和值引用

以前就知道JAVA對象分對象引用和值引用,並且還知道8種基礎數據類型,即引用時是值引用的數據類型,比如int,short,long,byte,float,double,char,boolean,其它都是對象引用。可是其它的對象引用我一直都以爲跟c裏面是一樣的指針傳遞,直到今天才發現原來JAVA裏面還是別有一番天地的。

 

    1. 方法調用的時候,並不是類似c的指針傳遞,而是引用的複製

比如代碼:

  1.     void func1(List s) {
  2.         s.add("dfdsa");
  3.     }
  4.     void test() {
  5.         List<String> list = new ArrayList<String>();
  6.         list.add("abc");
  7.         func1(list);
  8.         System.out.println(list.size()); // 此處結果爲2 
  9.     }

以前一直以爲在func1裏面的s跟外面的list變量是同一個引用(暫且理解爲指針好了)即在棧(stack)裏面是同一個東東,這個結論無可厚非,可是看代碼:

  1.     void func(String s) {
  2.     s += "tail";
  3.     }
  4.     void test() {
  5.         String a = "abc";
  6.         func(a);
  7.         System.out.println(a); // 此處結果爲abc  
  8.     }

經過討論才發現,原來在stack裏面a和func裏面的s是完全不同的兩個引用,雖然它們指向同一個堆(heap)裏面的對象,之所以跟上面的代碼結果看起來不一樣,只是因爲String是一個非可變類(immutable),簡單的說就是實例是不可被修改的。在func裏面執行s += "tail";操作的時候,s這個引用已經變成指向heap裏面另外一個值爲"abctail"的對象了,老的s引用已經被廢了,隨時可以被gc回收了

 

    2. String對象在內存中的位置

既然String是一個immutable的類,那麼對於同樣值的String實例,我們是可以不必重複創建的,於是就有了JVM中的String Pool的概念。簡單的說,String Pool裏面放着heap裏面String對象的引用。看代碼:

  1. String s = "abc";

當程序執行該代碼的時候,JVM會在String Pool裏面通過equal("abc")方法查找有沒有現成的String對象引用,如果沒有,則在heap裏面創建一個String對象並將該對象的引用保存到String Pool裏面;如果有了,那麼就直接返回該對象的引用。

 

再看一段非常類似的代碼:

  1. String s = new String("abc");

當程序執行該代碼的時候,JVM會像普通對象一樣生成這個String對象,在heap裏面保存,直接返回引用,並不會與String Pool交互,這樣一來,String Pool的優勢就沒有被髮揮了,怎麼辦呢?難道我們就不去使用new的方法創建String了嗎?答案是JVM還提供了一個方法:String.intern();來讓String Pool管理這種String對象。

intern方法的工作原理是這樣的:首先在heap裏面創建一個完全一樣的String對象,並且將該對象的引用放入String Pool中,最後返回給調用方,看代碼:

  1.         String s1=new String("abc"); 
  2.         String s2=s1.intern(); 
  3.         String s3="abc";
  4.         System.out.println(s1==s2); //false
  5.         System.out.println(s2==s3); //true
  6.         System.out.println(s1==s3); //false
  • s1引用的是heap裏面的一個普通String對象,在String Pool中沒有該對象的引用
  • s2是heap中另一個String對象的引用,並且該對象的引用已經存在在String Pool中了
  • s3在創建的時候JVM通過查找String Pool發現已經有一個同樣的對象,所以直接返回給s3一個到該對象的引用

結論:我們在寫JAVA代碼的時候要儘量避免使用String s = new String("abc");這種方式,因爲這樣產生的對象並沒有“註冊”到String Pool中,無法被重複使用,如果已經存在這種對象了,我們可以通過使用s = s.intern();的方式重新創建對象並“註冊”到String Pool中,方便後面的重複使用。

 

    3. 深入JVM內存的劃分

由於JVM對與heap和stack內存的使用有其特殊的規則,深入瞭解JVM是如何使用內存的,非常有助於我們在寫程序的時候搞清楚自己的對象到底在什麼地方,從而可以幫助我們在多線程程序和性能要求較高的程序中優化自己代碼,有興趣的同學可以參考sun的官方文檔(http://java.sun.com/docs/books/jvms/second_edition/html/Overview.doc.html#1732),下面僅就部分知識做簡單描述。

    a. 每個線程都有自己獨佔的stack,裏面存放的是當前線程執行的method及其局部變量

    b. heap中有部分是公共區域,存放的是類實例(class instance)和已分配內存的數組(array)

    c. heap中對於每個線程都有各自獨立的內存區域,存放以下內容:

        運行時常量池(runtime constant pool),上面提到的String Pool就屬於其中的一部分

        方法代碼(method code),即線程要執行的方法代碼

        靜態變量和方法(static variables and method),我們定義的static類型的變量和方法都存放在這裏

更詳細的描述可以參考圖片:

 

發佈了2 篇原創文章 · 獲贊 5 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章