Strong reference - 強引用
Soft Reference - 軟引用
Weak Reference - 弱引用
Phantom Reference - 虛引用
不同的引用類型有著不同的特性,同時也對應著不同的使用場景。
1.Strong reference - 強引用
實際編碼中最常見的一種引用類型。常見形式如:A a = new A();等。強引用本身存儲在棧內存中,其存儲指向對內存中對象的地址。一般情況下,當對內存中的對象不再有任何強引用指向它時,垃圾回收機器開始考慮可能要對此內存進行的垃圾回收。如當進行編碼:a = null,此時,剛剛在堆中分配地址并新建的a對象沒有其他的任何引用,當系統進行垃圾回收時,堆內存將被垃圾回收。
SoftReference、WeakReference、PhantomReference都是類java.lang.ref.Reference的子類。Reference作為抽象基類,定義了其子類對象的基本操作。Reference子類都具有如下特點:
1.Reference子類不能無參化直接創建,必須至少以強引用對象為構造參數,創建各自的子類對象;
2.因為1中以強引用對象為構造參數創建對象,因此,使得原本強引用所指向的堆內存中的對象將不再只與強引用本身直接關聯,與Reference的子類對象的引用也有一定聯系。且此種聯系將可能影響到對象的垃圾回收。
根據不同的子類對象對其指示對象(強引用所指向的堆內存中的對象)的垃圾回收不同的影響特點,分別形成了三個子類,即SoftReference、WeakReference和PhantomReference。
2.Soft Reference - 軟引用
軟引用的一般使用形式如下:
A a = new A();
SoftReference<A> srA = new SoftReference<A>(a);
通過對象的強引用為參數,創建了一個SoftReference對象,并使棧內存中的wrA指向此對象。
此時,進行如下編碼:a = null,對于原本a所指向的A對象的垃圾回收有什么影響呢?
先直接看一下下面一段程序的輸出結果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
import java.lang.ref.SoftReference; public class ReferenceTest { public static void main(String[] args) { A a = new A(); SoftReference<A> srA = new SoftReference<A>(a); a = null ; if (srA.get() == null ) { System.out.println( "a對象進入垃圾回收流程" ); } else { System.out.println( "a對象尚未被回收" + srA.get()); } // 垃圾回收 System.gc(); if (srA.get() == null ) { System.out.println( "a對象進入垃圾回收流程" ); } else { System.out.println( "a對象尚未被回收" + srA.get()); } } } class A { } |
##輸出結果為:
1
2
|
1 a對象尚未被回收A @4807ccf6 2 a對象尚未被回收A @4807ccf6 |
當 a = null后,堆內存中的A對象將不再有任何的強引用指向它,但此時尚存在srA引用的對象指向A對象。當第一次調用srA.get()方法返回此指示對象時,由于垃圾回收器很有可能尚未進行垃圾回收,此時get()是有結果的,這個很好理解。當程序執行System.gc();強制垃圾回收后,通過srA.get(),發現依然可以得到所指示的A對象,說明A對象并未被垃圾回收。那么,軟引用所指示的對象什么時候才開始被垃圾回收呢?需要滿足如下兩個條件:
1.當其指示的對象沒有任何強引用對象指向它;
2.當虛擬機內存不足時。
因此,SoftReference變相的延長了其指示對象占據堆內存的時間,直到虛擬機內存不足時垃圾回收器才回收此堆內存空間。
3.Weak Reference - 弱引用
同樣的,軟引用的一般使用形式如下:
A a = new A();
WeakReference<A> wrA = new WeakReference<A>(a);
當沒有任何強引用指向此對象時, 其垃圾回收又具有什么特性呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import java.lang.ref.WeakReference; public class ReferenceTest { public static void main(String[] args) { A a = new A(); WeakReference<A> wrA = new WeakReference<A>(a); a = null ; if (wrA.get() == null ) { System.out.println( "a對象進入垃圾回收流程" ); } else { System.out.println( "a對象尚未被回收" + wrA.get()); } // 垃圾回收 System.gc(); if (wrA.get() == null ) { System.out.println( "a對象進入垃圾回收流程" ); } else { System.out.println( "a對象尚未被回收" + wrA.get()); } } } class A { } |
##輸出結果為:
1
2
|
a對象尚未被回收A @52e5376a a對象進入垃圾回收流程 |
輸出的第一條結果解釋同上。當進行垃圾回收后,wrA.get()將返回null,表明其指示對象進入到了垃圾回收過程中。因此,對弱引用特點總結為:
WeakReference不改變原有強引用對象的垃圾回收時機,一旦其指示對象沒有任何強引用對象時,此對象即進入正常的垃圾回收流程。
那么,依據此特點,很可能有疑問:WeakReference存在又有什么意義呢?
其主要使用場景見于:當前已有強引用指向強引用對象,此時由于業務需要,需要增加對此對象的引用,同時又不希望改變此引用的垃圾回收時機,此時WeakReference正好符合需求,常見于一些與生命周期的場景中。
下面給出一個Android中關于WeakReference使用的場景 —— 結合靜態內部類和WeakReference來解決Activity中可能存在的Handler內存泄露問題。
Activity中我們需要新建一個線程獲取數據,使用handler - sendMessage方式。下面是這一過程的一般性代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public class MainActivity extends Activity { //... private int page; private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1 ) { //... page++; } else { //... } }; }; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); //... new Thread( new Runnable() { @Override public void run() { //.. Message msg = Message.obtain(); msg.what = 1 ; //msg.obj = xx; handler.sendMessage(msg); } }).start(); //... } } |
在Eclispe中Run Link,將會看到警示信息:This Handler class should be static or leaks might occur ...點擊查看此信息,其詳情中對問題進行了說明并給出了建議性的解決方案。
1
2
3
4
|
Issue: Ensures that Handler classes do not hold on to a reference to an outer class Id: HandlerLeak Since this Handler is declared as an inner class , it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class ;In the outer class , instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object. |
大致的意思是建議將Handler定義成內部靜態類,并在此靜態內部類中定義一個WeakReference的引用,由于指示外部的Activity對象。
問題分析:
Activity具有自身的生命周期,Activity中新開啟的線程運行過程中,可能此時用戶按下了Back鍵,或系統內存不足等希望回收此Activity,由于Activity中新起的線程并不會遵循Activity本身的什么周期,也就是說,當Activity執行了onDestroy,由于線程以及Handler 的HandleMessage的存在,使得系統本希望進行此Activity內存回收不能實現,因為非靜態內部類中隱性的持有對外部類的引用,導致可能存在的內存泄露問題。
因此,在Activity中使用Handler時,一方面需要將其定義為靜態內部類形式,這樣可以使其與外部類(Activity)解耦,不再持有外部類的引用,同時由于Handler中的handlerMessage一般都會多少需要訪問或修改Activity的屬性,此時,需要在Handler內部定義指向此Activity的WeakReference,使其不會影響到Activity的內存回收同時,可以在正常情況下訪問到Activity的屬性。
Google官方給出的建議寫法為:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
public class MainActivity extends Activity { //... private int page; private MyHandler mMyHandler = new MyHandler( this ); private static class MyHandler extends Handler { private WeakReference<MainActivity> wrActivity; public MyHandler(MainActivity activity) { this .wrActivity = new WeakReference<MainActivity>(activity); } @Override public void handleMessage(Message msg) { if (wrActivity.get() == null ) { return ; } MainActivity mActivity = wrActivity.get(); if (msg.what == 1 ) { //... mActivity.page++; } else { //... } } } @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); //... new Thread( new Runnable() { @Override public void run() { //.. Message msg = Message.obtain(); msg.what = 1 ; //msg.obj = xx; mMyHandler.sendMessage(msg); } }).start(); //... } } |
對于SoftReference和WeakReference,還有一個構造器參數為ReferenceQueue<T>,當SoftReference或WeakReference所指示的對象確實被垃圾回收后,其引用將被放置于ReferenceQueue中。注意上文中,當SoftReference或WeakReference的get()方法返回null時,僅是表明其指示的對象已經進入垃圾回收流程,此時對象不一定已經被垃圾回收。而只有確認被垃圾回收后,如果ReferenceQueue,其引用才會被放置于ReferenceQueue中。
看下面的一個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public class ReferenceTest { public static void main(String[] args) { A a = new A(); WeakReference<A> wrA = new WeakReference<A>(a); a = null ; if (wrA.get() == null ) { System.out.println( "a對象進入垃圾回收流程" ); } else { System.out.println( "a對象尚未被回收" + wrA.get()); } // 垃圾回收 System.gc(); if (wrA.get() == null ) { System.out.println( "a對象進入垃圾回收流程" ); } else { System.out.println( "a對象尚未被回收" + wrA.get()); } } } class A { @Override protected void finalize() throws Throwable { super .finalize(); System.out.println( "in A finalize" ); } } |
##輸出結果為:
1
2
3
|
1 a對象尚未被回收A @46993aaa 2 a對象被回收 3 in A finalize |
由此,也驗證了上文中的“進入垃圾回收流程”的說法。下面結合ReferenceQueue,看一段代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
public class ReferenceTest { public static void main(String[] args) { A a = new A(); ReferenceQueue<A> rq = new ReferenceQueue<A>(); WeakReference<A> wrA = new WeakReference<A>(a, rq); a = null ; if (wrA.get() == null ) { System.out.println( "a對象進入垃圾回收流程" ); } else { System.out.println( "a對象尚未被回收" + wrA.get()); } System.out.println( "rq item:" + rq.poll()); // 垃圾回收 System.gc(); if (wrA.get() == null ) { System.out.println( "a對象進入垃圾回收流程" ); } else { System.out.println( "a對象尚未被回收" + wrA.get()); } /* try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } */ System.out.println( "rq item:" + rq.poll()); } } class A { @Override protected void finalize() throws Throwable { super .finalize(); System.out.println( "in A finalize" ); } } |
##輸出結果為:
1
2
3
4
5
|
a對象尚未被回收A @302b2c81 rq item: null a對象進入垃圾回收流程 rq item: null in A finalize |
由此,驗證了“僅進入垃圾回收流程的SoftReference或WeakReference引用尚未被加入到ReferenceQueue”。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
public class ReferenceTest { public static void main(String[] args) { A a = new A(); ReferenceQueue<A> rq = new ReferenceQueue<A>(); WeakReference<A> wrA = new WeakReference<A>(a, rq); a = null ; if (wrA.get() == null ) { System.out.println( "a對象進入垃圾回收流程" ); } else { System.out.println( "a對象尚未被回收" + wrA.get()); } System.out.println( "rq item:" + rq.poll()); // 垃圾回收 System.gc(); if (wrA.get() == null ) { System.out.println( "a對象進入垃圾回收流程" ); } else { System.out.println( "a對象尚未被回收" + wrA.get()); } try { Thread.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( "rq item:" + rq.poll()); } } class A { @Override protected void finalize() throws Throwable { super .finalize(); System.out.println( "in A finalize" ); } } |
##輸出結果為:
1
2
3
4
5
|
a對象尚未被回收A @6276e1db rq item: null a對象進入垃圾回收流程 in A finalize rq item:java.lang.ref.WeakReference @645064f |
由此,證實了上述說法。
4.PhantomReference
與SoftReference或WeakReference相比,PhantomReference主要差別體現在如下幾點:
1.PhantomReference只有一個構造函數PhantomReference(T referent, ReferenceQueue<? super T> q),因此,PhantomReference使用必須結合ReferenceQueue;
2.不管有無強引用指向PhantomReference的指示對象,PhantomReference的get()方法返回結果都是null。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public class ReferenceTest { public static void main(String[] args) { A a = new A(); ReferenceQueue<A> rq = new ReferenceQueue<A>(); PhantomReference<A> prA = new PhantomReference<A>(a, rq); System.out.println( "prA.get():" + prA.get()); a = null ; System.gc(); try { Thread.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( "rq item:" + rq.poll()); } } class A { } |
##輸出結果為:
1
2
|
prA.get(): null rq item:java.lang.ref.PhantomReference @1da12fc0 |
代碼中的Thread.sleep(1);作用與上例中相同,都是確保垃圾回收線程能夠執行。否則,進進入垃圾回收流程而沒有真正被垃圾回收的指示對象的虛引用是不會被加入到PhantomReference中的。
與WeakReference相同,PhantomReference并不會改變其指示對象的垃圾回收時機。且可以總結出:ReferenceQueue的作用主要是用于監聽SoftReference/WeakReference/PhantomReference的指示對象是否已經被垃圾回收。
以上就是小編為大家帶來的Java/Android引用類型及其使用全面分析的全部內容了,希望對大家有所幫助,多多支持服務器之家~