本文主要分三部分介紹 Java 中的值、指針與引用的概念。
第一部分從編程語言的三種參數傳遞方式入手,闡釋「爲何 Java 中只有值傳遞」。
第二部分排除自動裝箱和自動拆箱的干擾,理解 Integer 等封裝類做爲參數傳值的情形。
第三部分經過簡單的示例,展現強引用、軟引用、弱引用和虛引用之間的區別。
形參是實參的拷貝,改變形參的值並不會影響外部實參的值。
從被調用函數的角度來講,值傳遞是單向的(實參->形參),參數的值只能傳入,不能傳出。java
public class IntegerTest01 { private static void changeInt(int value) { ++value; } public static void main(String[] args) { int a = 1; changeInt(a); System.out.println("a = " + a); } }
執行結果爲a = 1
ios
Java 中沒有指針,爲了直觀展現指針傳遞,這裏使用了 C++ 的例子。
指針從本質上講是一個變量,變量的值是另外一個變量的地址。所以能夠說指針傳遞屬於值傳遞。編程
#include <iostream> using namespace std; void fun(int *x) {// 聲明指針 *x += 5; // *x 是取得指針所指向的內存單元,即指針解引用 // x += 5; 則對實參沒有影響 } int main() { int y = 0; fun(&y);// 取地址 cout<< "y = "<< y <<endl; return 0; }
執行結果爲y = 5
segmentfault
《Head First Java》中關於 Java 參數傳遞的說明:數組
Java 中所傳遞的全部東西都是值,但此值是變量所攜帶的值。引用對象的變量所攜帶的是 遠程控制而不是對象自己,若你對方法傳入參數,實際上傳入的是遠程控制的拷貝。
《深刻理解 JVM 虛擬機》中關於 Sun HotSpot 虛擬機進行對象訪問的方式的說明:編程語言
若是使用直接指針,那麼 Java 堆對象的佈局中就必須考慮如何放置訪問對象類型數據的相關信息,而 reference 中存儲的直接就是對象地址。
在 Java 中聲明並初始化一個對象Object object = new Object()
,在堆中存儲對象實例數據,在棧中存儲對象地址,這裏的變量 object 至關於 C/C++ 中的指針。ide
所以,能夠經過 Java 對象的引用,達到指針傳遞的效果。函數
public class IntegerTest02 { private static void changeInt(int[] value) { ++value[0]; } public static void main(String[] args) { int[] a = {1}; changeInt(a); System.out.println("a[0] = " + a[0]); } }
執行結果爲a[0] = 2
佈局
既然 Java 中沒有引用傳遞,那麼到底什麼是引用傳遞呢,看下 C++ 中的例子。ui
#include <iostream> using namespace std; void fun(int &x){// 聲明一個別名 x += 5; // 修改的是 x 引用的對象值 &x = y; } int main() { int y = 0; fun(y); cout<< "y = "<< y <<endl; return 0; }
執行結果y = 5
C++ 中的引用就是某一變量(目標)的一個別名,對引用的操做與對變量直接操做徹底同樣。
聲明一個引用,不是新定義了一個變量,它只表示該引用名是目標變量名的一個別名,它自己不是一種數據類型,所以引用自己不佔存儲單元,系統也不給引用分配存儲單元。
Java 中的引用是 reference 類型,相似於 C/C++ 中指針的概念,而跟 C/C++ 中引用的概念徹底不一樣。
在 JDK 1.2 之前,Java 中的引用的定義:若是 reference 類型的數據中存儲的數值表明的是另一塊內存的起始地址,就稱這塊內存表明着一個引用。
在JDK 1.2以後,Java對引用的概念進行了擴充,將引用分爲強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4種,這4種引用強度依次逐漸減弱。
進一步的介紹見 Java 中的 Reference 類型
回到開篇值傳遞的例子:
public class IntegerTest01 { private static void changeInt(int value) { ++value; } public static void main(String[] args) { int a = 1; changeInt(a); System.out.println("a = " + a); } }
若是把代碼中的 int 類型換成 Integer 對象,結果會怎麼樣?
public class IntegerTest02 { private static void changeInteger(Integer value) { ++value; } public static void main(String[] args) { Integer a = 1; changeInteger(a); System.out.println("a = " + a); } }
首先須要排除自動裝箱和自動拆箱的干擾。
package com.sumkor.jdk7.integer02; public class IntegerTest { public static void main(String[] args) { Integer a = 1; int b = a; } }
使用命令javap -c IntegerTest.class
進行反編譯:
Compiled from "IntegerTest.java" public class com.sumkor.jdk7.integer02.IntegerTest { public com.sumkor.jdk7.integer02.IntegerTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: iconst_1 1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: astore_1 5: aload_1 6: invokevirtual #3 // Method java/lang/Integer.intValue:()I 9: istore_2 10: return }
由此可知:
自動裝箱實際調用的是Integer.valueOf
自動拆箱實際調用的是Integer.intValue
所以,排除自動裝箱、自動拆箱,例子 IntegerTest02 等價於如下寫法:
public class IntegerTest03 { private static void changeInteger(Integer value) { value = Integer.valueOf(value.intValue() + 1); } public static void main(String[] args) { Integer a = Integer.valueOf(1); changeInteger(a); } }
查看 Integer 源碼,可知valueOf()
會將形參指向不一樣的 Integer 對象實例。
/** * Returns an {@code Integer} instance representing the specified * {@code int} value. If a new {@code Integer} instance is not * required, this method should generally be used in preference to * the constructor {@link #Integer(int)}, as this method is likely * to yield significantly better space and time performance by * caching frequently requested values. * * This method will always cache values in the range -128 to 127, * inclusive, and may cache other values outside of this range. * * @param i an {@code int} value. * @return an {@code Integer} instance representing {@code i}. * @since 1.5 */ public static Integer valueOf(int i) { assert IntegerCache.high >= 127; if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } /** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. The size of the cache * may be controlled by the -XX:AutoBoxCacheMax=<size> option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * sun.misc.VM class. */ private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); } private IntegerCache() {} }
IntegerCache 在首次使用時被初始化,最小值爲 -128,最大值默認爲 127,也能夠經過 VM 參數-XX:AutoBoxCacheMax=<size>
設置最大值。
@Test public void test01() { Integer a = 1; Integer b = 1; System.out.println(a == b); Integer aa = 128; Integer bb = 128; System.out.println(aa == bb); }
變量a
和b
指向的是同一個IntegerCache.cache
,所以比較結果爲true
.
變量aa
和bb
指向的是不一樣的 Integer 實例,所以比較結果爲false
.
《深刻理解 JVM 虛擬機》中對此的介紹爲:
Object object = new Object()
這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。Reference 類型的強度跟 JVM 垃圾回收有關,惋惜書上沒有給出實例,本文對此進行補充。
注意,如下例子中,使用 JDK 1.8,且均設置 JVM 參數爲-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
即堆大小爲 20 m,其中新生代大小爲 10 m,按照 1:8 比例分配,Eden 區大小爲 8 m。
/** * Created by Sumkor on 2018/9/10. */ public class StrongReferenceTest { public static void main(String[] args) { byte[] allocation01 = new byte[1024 * 1024 * 9]; byte[] allocation02 = new byte[1024 * 1024 * 9]; } }
執行結果以下,可知垃圾收集器寧願拋出內存溢出異常,也不會回收正在使用中的強引用:
[GC (Allocation Failure) 11197K->10032K(19456K), 0.0014301 secs] [Full GC (Ergonomics) 10032K->9851K(19456K), 0.0072375 secs] [GC (Allocation Failure) 9851K->9851K(19456K), 0.0004413 secs] [Full GC (Allocation Failure) 9851K->9833K(19456K), 0.0093839 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.sumkor.reference.StrongReferenceTest.main(StrongReferenceTest.java:18)
@Test public void test01() { byte[] allocation01 = new byte[1024 * 1024 * 8]; SoftReference<byte[]> softReference = new SoftReference<byte[]>(allocation01); // 此時,對於這個byte數組對象,有兩個引用路徑,一個是來自SoftReference對象的軟引用,一個來自變量allocation01的強引用,因此這個數組對象是強可及對象。 System.out.println("softReference.get() = " + softReference.get()); allocation01 = null; // 結束變量allocation01對這個byte數組實例的強引用,此後該byte數組對象變成一個軟可及對象,能夠經過softReference進行訪問 System.out.println("softReference.get() = " + softReference.get()); System.gc(); System.out.println("softReference.get() = " + softReference.get()); }
執行結果以下,可見在觸發 gc 時,內存空間充足,並不會回收軟引用:
softReference.get() = [B@5d6f64b1 softReference.get() = [B@5d6f64b1 [GC (System.gc()) 14584K->9644K(19456K), 0.0040375 secs] [Full GC (System.gc()) 9644K->9508K(19456K), 0.0115994 secs] softReference.get() = [B@5d6f64b1
再來看內存不足的例子:
@Test public void test02() { byte[] allocation01 = new byte[1024 * 1024 * 8]; SoftReference<byte[]> softReference = new SoftReference<byte[]>(allocation01); // 此時,對於這個byte數組對象,有兩個引用路徑,一個是來自SoftReference對象的軟引用,一個來自變量allocation01的強引用,因此這個數組對象是強可及對象。 System.out.println("softReference.get() = " + softReference.get()); allocation01 = null; // 結束變量allocation01對這個byte數組實例的強引用,此後該byte數組對象變成一個軟可及對象,能夠經過softReference進行訪問 System.out.println("softReference.get() = " + softReference.get()); byte[] allocation02 = new byte[1024 * 1024 * 8]; System.out.println("softReference.get() = " + softReference.get()); }
可見在觸發 gc 時,內存空間不足,回收軟引用:
softReference.get() = [B@5d6f64b1 softReference.get() = [B@5d6f64b1 [GC (Allocation Failure) 14749K->9636K(19456K), 0.0056237 secs] [GC (Allocation Failure) 9636K->9684K(19456K), 0.0014787 secs] [Full GC (Allocation Failure) 9684K->9508K(19456K), 0.0128735 secs] [GC (Allocation Failure) 9508K->9508K(19456K), 0.0006353 secs] [Full GC (Allocation Failure) 9508K->1261K(19456K), 0.0107362 secs] softReference.get() = null
package com.sumkor.reference; import java.lang.ref.WeakReference; /** * Created by Sumkor on 2018/9/10. */ public class WeakReferenceTest { public static void main(String[] args) { byte[] allocation01 = new byte[1024 * 1024 * 8]; WeakReference<byte[]> weakReference = new WeakReference<byte[]>(allocation01); System.out.println("weakReference.get() = " + weakReference.get());// [B@154ebadd allocation01 = null; System.out.println("weakReference.get() = " + weakReference.get());// [B@154ebadd System.gc(); System.out.println("weakReference.get() = " + weakReference.get());// null } }
執行結果以下,可見儘管內存空間充足,垃圾回收器工做時回收掉只被弱引用關聯的對象:
weakReference.get() = [B@14ae5a5 weakReference.get() = [B@14ae5a5 [GC (System.gc()) 10177K->9008K(19456K), 0.0011390 secs] [Full GC (System.gc()) 9008K->643K(19456K), 0.0069800 secs] weakReference.get() = null
package com.sumkor.reference; import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.reflect.Field; /** * Created by Sumkor on 2018/9/10. */ public class PhantomReferenceTest { public static void main(String[] args) throws InterruptedException { ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); byte[] allocation01 = new byte[1024 * 1024 * 8]; PhantomReference<byte[]> phantom = new PhantomReference<>(allocation01, referenceQueue); allocation01 = null; Thread.currentThread().sleep(3000); System.gc(); Thread.currentThread().sleep(3000); Reference<?> poll = referenceQueue.poll(); System.out.println("poll = " + poll);// java.lang.ref.PhantomReference@5d6f64b1 System.out.println("phantom.get() = " + phantom.get()); } }
執行結果以下,phantom.get()
老是爲 null,當 byte 數組對象被垃圾回收器回收時,垃圾收集器會把要回收的對象添加到引用隊列ReferenceQueue,即獲得一個「通知」:
[GC (System.gc()) 14742K->9608K(19456K), 0.0025841 secs] [Full GC (System.gc()) 9608K->9510K(19456K), 0.0117227 secs] poll = java.lang.ref.PhantomReference@5d6f64b1 phantom.get() = null