摘要:強(qiáng)引用執(zhí)行結(jié)果如下,可知垃圾收集器寧愿拋出內(nèi)存溢出異常,也不會回收正在使用中的強(qiáng)引用軟引用此時,對于這個數(shù)組對象,有兩個引用路徑,一個是來自對象的軟引用,一個來自變量的強(qiáng)引用,所以這個數(shù)組對象是強(qiáng)可及對象。
本文主要分三部分介紹 Java 中的值、指針與引用的概念。一、參數(shù)傳遞方式 1.1 值傳遞
第一部分從編程語言的三種參數(shù)傳遞方式入手,闡釋“為什么 Java 中只有值傳遞”。
第二部分排除自動裝箱和自動拆箱的干擾,理解 Integer 等封裝類作為參數(shù)傳值的情形。
第三部分通過簡單的示例,展示強(qiáng)引用、軟引用、弱引用和虛引用之間的區(qū)別。
形參是實(shí)參的拷貝,改變形參的值并不會影響外部實(shí)參的值。
從被調(diào)用函數(shù)的角度來說,值傳遞是單向的(實(shí)參->形參),參數(shù)的值只能傳入,不能傳出。
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); } }
執(zhí)行結(jié)果為a = 1
1.2 指針傳遞Java 中沒有指針,為了直觀展示指針傳遞,這里使用了 C++ 的例子。
指針從本質(zhì)上講是一個變量,變量的值是另一個變量的地址。因此可以說指針傳遞屬于值傳遞。
#includeusing namespace std; void fun(int *x) {// 聲明指針 *x += 5; // *x 是取得指針?biāo)赶虻膬?nèi)存單元,即指針解引用 // x += 5; 則對實(shí)參沒有影響 } int main() { int y = 0; fun(&y);// 取地址 cout<< "y = "<< y < 執(zhí)行結(jié)果為y = 5
Java 中的“指針”《Head First Java》中關(guān)于 Java 參數(shù)傳遞的說明:
Java 中所傳遞的所有東西都是值,但此值是變量所攜帶的值。引用對象的變量所攜帶的是遠(yuǎn)程控制而不是對象本身,若你對方法傳入?yún)?shù),實(shí)際上傳入的是遠(yuǎn)程控制的拷貝。《深入理解 JVM 虛擬機(jī)》中關(guān)于 Sun HotSpot 虛擬機(jī)進(jìn)行對象訪問的方式的說明:
如果使用直接指針,那么 Java 堆對象的布局中就必須考慮如何放置訪問對象類型數(shù)據(jù)的相關(guān)信息,而 reference 中存儲的直接就是對象地址。在 Java 中聲明并初始化一個對象Object object = new Object(),在堆中存儲對象實(shí)例數(shù)據(jù),在棧中存儲對象地址,這里的變量 object 相當(dāng)于 C/C++ 中的指針。
因此,可以通過 Java 對象的引用,達(dá)到指針傳遞的效果。
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]); } }執(zhí)行結(jié)果為a[0] = 2
1.3 引用傳遞既然 Java 中沒有引用傳遞,那么到底什么是引用傳遞呢,看下 C++ 中的例子。
#includeusing namespace std; void fun(int &x){// 聲明一個別名 x += 5; // 修改的是 x 引用的對象值 &x = y; } int main() { int y = 0; fun(y); cout<< "y = "<< y < 執(zhí)行結(jié)果y = 5
C++ 中的引用就是某一變量(目標(biāo))的一個別名,對引用的操作與對變量直接操作完全一樣。
Java 中的引用
聲明一個引用,不是新定義了一個變量,它只表示該引用名是目標(biāo)變量名的一個別名,它本身不是一種數(shù)據(jù)類型,因此引用本身不占存儲單元,系統(tǒng)也不給引用分配存儲單元。Java 中的引用是 reference 類型,類似于 C/C++ 中指針的概念,而跟 C/C++ 中引用的概念完全不同。
在 JDK 1.2 以前,Java 中的引用的定義:如果 reference 類型的數(shù)據(jù)中存儲的數(shù)值代表的是另外一塊內(nèi)存的起始地址,就稱這塊內(nèi)存代表著一個引用。
在JDK 1.2之后,Java對引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4種,這4種引用強(qiáng)度依次逐漸減弱。
進(jìn)一步的介紹見 Java 中的 Reference 類型
二、Integer 參數(shù)傳遞問題回到開篇值傳遞的例子:
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 對象,結(jié)果會怎么樣?
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); } }首先需要排除自動裝箱和自動拆箱的干擾。
2.1 自動裝箱和自動拆箱package com.sumkor.jdk7.integer02; public class IntegerTest { public static void main(String[] args) { Integer a = 1; int b = a; } }使用命令javap -c IntegerTest.class進(jìn)行反編譯:
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."":()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 } 由此可知:
自動裝箱實(shí)際調(diào)用的是Integer.valueOf
自動拆箱實(shí)際調(diào)用的是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 對象實(shí)例。
/** * 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=2.2 關(guān)于 IntegerCacheoption. * 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,最大值默認(rèn)為 127,也可以通過 VM 參數(shù)-XX:AutoBoxCacheMax=
設(shè)置最大值。 @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,因此比較結(jié)果為true.
三、Java 中的 Reference 類型
變量aa和bb指向的是不同的 Integer 實(shí)例,因此比較結(jié)果為false.《深入理解 JVM 虛擬機(jī)》中對此的介紹為:
強(qiáng)引用就是指在程序代碼之中普遍存在的,類似Object object = new Object()這類的引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會回收掉被引用的對象。
軟引用是用來描述一些還有用但并非必需的對象。對于軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收。如果這次回收還沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常。在JDK 1.2之后,提供了 SoftReference 類來實(shí)現(xiàn)軟引用。
弱引用也是用來描述非必需對象的,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時,無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象。在JDK 1.2之后,提供了 WeakReference 類來實(shí)現(xiàn)弱引用。
虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系。一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來取得一個對象實(shí)例。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知。在JDK 1.2之后,提供了 PhantomReference 類來實(shí)現(xiàn)虛引用。
Reference 類型的強(qiáng)度跟 JVM 垃圾回收有關(guān),可惜書上沒有給出實(shí)例,本文對此進(jìn)行補(bǔ)充。注意,以下例子中,使用 JDK 1.8,且均設(shè)置 JVM 參數(shù)為-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8
3.1 強(qiáng)引用
即堆大小為 20 m,其中新生代大小為 10 m,按照 1:8 比例分配,Eden 區(qū)大小為 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]; } }執(zhí)行結(jié)果如下,可知垃圾收集器寧愿拋出內(nèi)存溢出異常,也不會回收正在使用中的強(qiáng)引用:
[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)3.2 軟引用@Test public void test01() { byte[] allocation01 = new byte[1024 * 1024 * 8]; SoftReferencesoftReference = new SoftReference (allocation01); // 此時,對于這個byte數(shù)組對象,有兩個引用路徑,一個是來自SoftReference對象的軟引用,一個來自變量allocation01的強(qiáng)引用,所以這個數(shù)組對象是強(qiáng)可及對象。 System.out.println("softReference.get() = " + softReference.get()); allocation01 = null; // 結(jié)束變量allocation01對這個byte數(shù)組實(shí)例的強(qiáng)引用,此后該byte數(shù)組對象變成一個軟可及對象,可以通過softReference進(jìn)行訪問 System.out.println("softReference.get() = " + softReference.get()); System.gc(); System.out.println("softReference.get() = " + softReference.get()); } 執(zhí)行結(jié)果如下,可見在觸發(fā) gc 時,內(nèi)存空間充足,并不會回收軟引用:
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再來看內(nèi)存不足的例子:
@Test public void test02() { byte[] allocation01 = new byte[1024 * 1024 * 8]; SoftReferencesoftReference = new SoftReference (allocation01); // 此時,對于這個byte數(shù)組對象,有兩個引用路徑,一個是來自SoftReference對象的軟引用,一個來自變量allocation01的強(qiáng)引用,所以這個數(shù)組對象是強(qiáng)可及對象。 System.out.println("softReference.get() = " + softReference.get()); allocation01 = null; // 結(jié)束變量allocation01對這個byte數(shù)組實(shí)例的強(qiáng)引用,此后該byte數(shù)組對象變成一個軟可及對象,可以通過softReference進(jìn)行訪問 System.out.println("softReference.get() = " + softReference.get()); byte[] allocation02 = new byte[1024 * 1024 * 8]; System.out.println("softReference.get() = " + softReference.get()); } 可見在觸發(fā) gc 時,內(nèi)存空間不足,回收軟引用:
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() = null3.3 弱引用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]; WeakReferenceweakReference = new WeakReference (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 } } 執(zhí)行結(jié)果如下,可見盡管內(nèi)存空間充足,垃圾回收器工作時回收掉只被弱引用關(guān)聯(lián)的對象:
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() = null3.4 虛引用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執(zhí)行結(jié)果如下,phantom.get()總是為 null,當(dāng) byte 數(shù)組對象被垃圾回收器回收時,垃圾收集器會把要回收的對象添加到引用隊列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
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/77076.html
摘要:有種流行的觀點(diǎn)說的另外一個特殊之處在于,在方法調(diào)用傳參數(shù)時,是按值傳遞的,其他普通對象是引用傳遞。然而這種說法是大大錯誤的,至少是完全誤解了值傳遞和引用傳遞的概念。方法調(diào)用傳參只有一種傳遞就是值傳遞。 上篇文章說到Java的String是比較特殊的對象,它是不可變的。 有種流行的觀點(diǎn)說String的另外一個特殊之處在于,在方法調(diào)用傳參數(shù)時,String是按值傳遞的,其他普通對象是引用傳...
摘要:引用數(shù)據(jù)類型指針存放在局部變量表中,調(diào)用方法的時候,副本引用壓棧,賦值僅改變副本的引用。方法執(zhí)行完畢,不再局部變量不再被使用到,等待被回收。 小方法大門道 小瓜瓜作為一個Java初學(xué)者,今天跟我說她想通過一個Java方法,將外部變量通過參數(shù)傳遞到方法中去,進(jìn)行邏輯處理,方法執(zhí)行完畢之后,再對修改過的變量進(jìn)行判斷處理,代碼如下所示。 public class MethodParamsPa...
摘要:每個棧幀中包括局部變量表用來存儲方法中的局部變量非靜態(tài)變量函數(shù)形參。操作數(shù)棧虛擬機(jī)的解釋執(zhí)行引擎被稱為基于棧的執(zhí)行引擎,其中所指的棧就是指操作數(shù)棧。指向運(yùn)行時常量池的引用存儲程序執(zhí)行時可能用到常量的引用。 本篇文章轉(zhuǎn)自微信公眾號:Java后端技術(shù) 學(xué)過Java基礎(chǔ)的人都知道:值傳遞和引用傳遞是初次接觸Java時的一個難點(diǎn),有時候記得了語法卻記不得怎么實(shí)際運(yùn)用,有時候會的了運(yùn)用卻解釋不出...
摘要:操作數(shù)棧虛擬機(jī)的解釋執(zhí)行引擎被稱為基于棧的執(zhí)行引擎,其中所指的棧就是指操作數(shù)棧。基本數(shù)據(jù)類型的靜態(tài)變量前面提到方法區(qū)用來存儲一些共享數(shù)據(jù),因此基本數(shù)據(jù)類型的靜態(tài)變量名以及值存儲于方法區(qū)的運(yùn)行時常 本文旨在用最通俗的語言講述最枯燥的基本知識 學(xué)過Java基礎(chǔ)的人都知道:值傳遞和引用傳遞是初次接觸Java時的一個難點(diǎn),有時候記得了語法卻記不得怎么實(shí)際運(yùn)用,有時候會的了運(yùn)用卻解釋不出原理,而...
摘要:接下了,我們調(diào)用方法,來嘗試改變的值以此驗證中的傳值方式。我們將作為實(shí)參傳給方法,形參來接受這個實(shí)參,在這里就體現(xiàn)出了兩種傳參方式的不同。中只有值傳遞這一種方式,只不過對于引用類型來說,傳遞的參數(shù)是對象的引用罷了。 前言 這幾天在整理java基礎(chǔ)知識方面的內(nèi)容,對于值傳遞還不是特別理解,于是查閱了一些資料和網(wǎng)上相關(guān)博客,自己進(jìn)行了歸納總結(jié),最后將其整理成了一篇博客。 值傳遞 值傳遞是指...
閱讀 1240·2021-11-22 13:54
閱讀 1425·2021-11-22 09:34
閱讀 2698·2021-11-22 09:34
閱讀 4008·2021-10-13 09:39
閱讀 3342·2019-08-26 11:52
閱讀 3361·2019-08-26 11:50
閱讀 1529·2019-08-26 10:56
閱讀 1913·2019-08-26 10:44