java內(nèi)存管理分為內(nèi)存分配和內(nèi)存回收,都不需要程序員負責,垃圾回收的機制主要是看對象是否有引用指向該對象。
java對象的引用包括
強引用,軟引用,弱引用,虛引用
java中提供這四種引用類型主要有兩個目的:
第一是可以讓程序員通過代碼的方式?jīng)Q定某些對象的生命周期;
第二是有利于jvm進行垃圾回收。
下面來闡述一下這四種類型引用的概念:
1.強引用
是指創(chuàng)建一個對象并把這個對象賦給一個引用變量。
比如:
1
2
|
object object = new object(); string str = "hello" ; |
強引用有引用變量指向時永遠不會被垃圾回收,jvm寧愿拋出outofmemory錯誤也不會回收這種對象。
1
2
3
4
5
6
7
8
9
|
public class main { public static void main(string[] args) { new main()fun1(); } public void fun1() { object object = new object(); object[] objarr = new object[ 1000 ]; } |
當運行至object[] objarr = new object[1000];這句時,如果內(nèi)存不足,jvm會拋出oom錯誤也不會回收object指向的對象。不過要注意的是,當fun1運行完之后,object和objarr都已經(jīng)不存在了,所以它們指向的對象都會被jvm回收。
如果想中斷強引用和某個對象之間的關(guān)聯(lián),可以顯示地將引用賦值為null,這樣一來的話,jvm在合適的時間就會回收該對象。
比如vector類的clear方法中就是通過將引用賦值為null來實現(xiàn)清理工作的:
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
|
/** * removes the element at the specified position in this vector * shifts any subsequent elements to the left (subtracts one from their * indices) returns the element that was removed from the vector * * @throws arrayindexoutofboundsexception if the index is out of range * ({@code index < 0 || index >= size()}) * @param index the index of the element to be removed * @return element that was removed * @since 2 */ public synchronized e remove( int index) { modcount++; if (index >= elementcount) throw new arrayindexoutofboundsexception(index); object oldvalue = elementdata[index]; int nummoved = elementcount - index - 1 ; if (nummoved > 0 ) systemarraycopy(elementdata, index+ 1 , elementdata, index, nummoved); elementdata[--elementcount] = null ; // let gc do its work return (e)oldvalue; } |
2.軟引用(softreference)
如果一個對象具有軟引用,內(nèi)存空間足夠,垃圾回收器就不會回收它;
如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。
軟引用可用來實現(xiàn)內(nèi)存敏感的高速緩存,比如網(wǎng)頁緩存、圖片緩存等。使用軟引用能防止內(nèi)存泄露,增強程序的健壯性。
softreference的特點是它的一個實例保存對一個java對象的軟引用, 該軟引用的存在不妨礙垃圾收集線程對該java對象的回收。
也就是說,一旦softreference保存了對一個java對象的軟引用后,在垃圾線程對 這個java對象回收前,softreference類所提供的get()方法返回java對象的強引用。
另外,一旦垃圾線程回收該java對象之 后,get()方法將返回null。
舉個栗子:
1
2
|
myobject aref = new myobject(); softreference asoftref= new softreference(aref); |
此時,對于這個myobject對象,有兩個引用路徑,一個是來自softreference對象的軟引用,一個來自變量areference的強引用,所以這個myobject對象是強可及對象。
隨即,我們可以結(jié)束areference對這個myobject實例的強引用:
1
|
aref = null ; |
此后,這個myobject對象成為了軟引用對象。如果垃圾收集線程進行內(nèi)存垃圾收集,并不會因為有一個softreference對該對象的引用而始終保留該對象。
java虛擬機的垃圾收集線程對軟可及對象和其他一般java對象進行了區(qū)別對待:軟可及對象的清理是由垃圾收集線程根據(jù)其特定算法按照內(nèi)存需求決定的。
也就是說,垃圾收集線程會在虛擬機拋出outofmemoryerror之前回收軟可及對象,而且虛擬機會盡可能優(yōu)先回收長時間閑置不用的軟可及對象,對那些剛剛構(gòu)建的或剛剛使用過的“新”軟可反對象會被虛擬機盡可能保留。在回收這些對象之前,我們可以通過:
1
|
myobject anotherref=(myobject)asoftref.get(); |
重新獲得對該實例的強引用。而回收之后,調(diào)用get()方法就只能得到null了。
使用referencequeue清除失去了軟引用對象的softreference:
作為一個java對象,softreference對象除了具有保存軟引用的特殊性之外,也具有java對象的一般性。所以,當軟可及對象被回收之后,雖然這個softreference對象的get()方法返回null,但這個softreference對象已經(jīng)不再具有存在的價值,需要一個適當?shù)那宄龣C制,避免大量softreference對象帶來的內(nèi)存泄漏。在java.lang.ref包里還提供了referencequeue。如果在創(chuàng)建softreference對象的時候,使用了一個referencequeue對象作為參數(shù)提供給softreference的構(gòu)造方法,如:
1
2
|
referencequeue queue = new referencequeue(); softreference ref= new softreference(amyobject, queue); |
那么當這個softreference所軟引用的amyohject被垃圾收集器回收的同時,ref所強引用的softreference對象被列入referencequeue。也就是說,referencequeue中保存的對象是reference對象,而且是已經(jīng)失去了它所軟引用的對象的reference對象。另外從referencequeue這個名字也可以看出,它是一個隊列,當我們調(diào)用它的poll()方法的時候,如果這個隊列中不是空隊列,那么將返回隊列前面的那個reference對象。
在任何時候,我們都可以調(diào)用referencequeue的poll()方法來檢查是否有它所關(guān)心的非強可及對象被回收。如果隊列為空,將返回一個null,否則該方法返回隊列中前面的一個reference對象。利用這個方法,我們可以檢查哪個softreference所軟引用的對象已經(jīng)被回收。于是我們可以把這些失去所軟引用的對象的softreference對象清除掉。常用的方式為:
1
2
3
4
|
softreference ref = null ; while ((ref = (employeeref) qpoll()) != null ) { // 清除ref } |
3.弱引用(weakreference)
弱引用也是用來描述非必需對象的,當jvm進行垃圾回收時,無論內(nèi)存是否充足,都會回收被弱引用關(guān)聯(lián)的對象。在java中,用java.lang.ref.weakreference類來表示。下面是使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class test { public static void main(string[] args) { weakreference<people>reference= new weakreference<people>( new people( "zhouqian" , 20 )); systemoutprintln(referenceget()); systemgc(); //通知gvm回收資源 systemoutprintln(referenceget()); } } class people{ public string name; public int age; public people(string name, int age) { thisname=name; thisage=age; } @override public string tostring() { return "[name:" +name+ ",age:" +age+ "]" ; } } |
輸出結(jié)果:
[name:zhouqian,age:20]
null
第二個輸出結(jié)果是null,這說明只要jvm進行垃圾回收,被弱引用關(guān)聯(lián)的對象必定會被回收掉。不過要注意的是,這里所說的被弱引用關(guān)聯(lián)的對象是指只有弱引用與之關(guān)聯(lián),如果存在強引用同時與之關(guān)聯(lián),則進行垃圾回收時也不會回收該對象(軟引用也是如此)。
比如:將代碼做一點小更改:
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
|
package yinyong; import javalangrefweakreference; public class test { public static void main(string[] args) { people people= new people( "zhouqian" , 20 ); weakreference<people>reference= new weakreference<people>(people); //<span style="color:#ff0000;">關(guān)聯(lián)強引用</span> systemoutprintln(referenceget()); systemgc(); systemoutprintln(referenceget()); } } class people{ public string name; public int age; public people(string name, int age) { thisname=name; thisage=age; } @override public string tostring() { return "[name:" +name+ ",age:" +age+ "]" ; } } //結(jié)果發(fā)生了很大的變化 [name:zhouqian,age: 20 ] [name:zhouqian,age: 20 ] |
弱引用可以和一個引用隊列(referencequeue)聯(lián)合使用,如果弱引用所引用的對象被jvm回收,這個軟引用就會被加入到與之關(guān)聯(lián)的引用隊列中。
4.虛引用(phantomreference)
虛引用和前面的軟引用、弱引用不同,它并不影響對象的生命周期。在java中用java.lang.ref.phantomreference類表示。如果一個對象與虛引用關(guān)聯(lián),則跟沒有引用與之關(guān)聯(lián)一樣,在任何時候都可能被垃圾回收器回收。
要注意的是,虛引用必須和引用隊列關(guān)聯(lián)使用,當垃圾回收器準備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用,就會把這個虛引用加入到與之 關(guān)聯(lián)的引用隊列中。程序可以通過判斷引用隊列中是否已經(jīng)加入了虛引用,來了解被引用的對象是否將要被垃圾回收。如果程序發(fā)現(xiàn)某個虛引用已經(jīng)被加入到引用隊列,那么就可以在所引用的對象的內(nèi)存被回收之前采取必要的行動。
1
2
3
4
5
6
7
8
9
|
import javalangrefphantomreference; import javalangrefreferencequeue; public class main { public static void main(string[] args) { referencequeue<string> queue = new referencequeue<string>(); phantomreference<string> pr = new phantomreference<string>( new string( "hello" ), queue); systemoutprintln(prget()); } } |
軟引用和弱引用
對于強引用,我們平時在編寫代碼時經(jīng)常會用到。而對于其他三種類型的引用,使用得最多的就是軟引用和弱引用,這2種既有相似之處又有區(qū)別。它們都是用來描述非必需對象的,但是被軟引用關(guān)聯(lián)的對象只有在內(nèi)存不足時才會被回收,而被弱引用關(guān)聯(lián)的對象在jvm進行垃圾回收時總會被回收。
在softreference類中,有三個方法,兩個構(gòu)造方法和一個get方法(wekreference類似):
兩個構(gòu)造方法:
1
2
3
4
5
6
7
8
|
public softreference(t referent) { super (referent); thistimestamp = clock; } public softreference(t referent, referencequeue<? super t> q) { super (referent, q); thistimestamp = clock; } |
get方法用來獲取與軟引用關(guān)聯(lián)的對象的引用,如果該對象被回收了,則返回null。
在使用軟引用和弱引用的時候,我們可以顯示地通過system.gc()來通知jvm進行垃圾回收,但是要注意的是,雖然發(fā)出了通知,jvm不一定會立刻執(zhí)行,也就是說這句是無法確保此時jvm一定會進行垃圾回收的。
對象可及性的判斷
在很多時候,一個對象并不是從根集直接引用的,而是一個對象被其他對象引用,甚至同時被幾個對象所引用,從而構(gòu)成一個以根集為頂?shù)臉湫谓Y(jié)構(gòu)。如圖2所示
在這個樹形的引用鏈中,箭頭的方向代表了引用的方向,所指向的對象是被引用對象。由圖可以看出,從根集到一個對象可以由很多條路徑。比如到達對象5的路徑就有①-⑤,③-⑦兩條路徑。由此帶來了一個問題,那就是某個對象的可及性如何判斷:
- 單條引用路徑可及性判斷:在這條路徑中,最弱的一個引用決定對象的可及性。
- 多條引用路徑可及性判斷:幾條路徑中,最強的一條的引用決定對象的可及性。
比如,我們假設(shè)圖2中引用①和③為強引用,⑤為軟引用,⑦為弱引用,對于對象5按照這兩個判斷原則,路徑①-⑤取最弱的引用⑤,因此該路徑對對象5的引用為軟引用。同樣,③-⑦為弱引用。在這兩條路徑之間取最強的引用,于是對象5是一個軟可及對象
如何利用軟引用和弱引用解決oom問題
前面講了關(guān)于軟引用和弱引用相關(guān)的基礎(chǔ)知識,那么到底如何利用它們來優(yōu)化程序性能,從而避免oom的問題呢?
下面舉個例子,假如有一個應用需要讀取大量的本地圖片,如果每次讀取圖片都從硬盤讀取,則會嚴重影響性能,但是如果全部加載到內(nèi)存當中,又有可能造成內(nèi)存溢出,此時使用軟引用可以解決這個問題。
設(shè)計思路是:用一個hashmap來保存圖片的路徑 和 相應圖片對象關(guān)聯(lián)的軟引用之間的映射關(guān)系,在內(nèi)存不足時,jvm會自動回收這些緩存圖片對象所占用的空間,從而有效地避免了oom的問題。在android開發(fā)中對于大量圖片下載會經(jīng)常用到。
使用軟引用構(gòu)建敏感數(shù)據(jù)的緩存
1 為什么需要使用軟引用
首先,我們看一個雇員信息查詢系統(tǒng)的實例。我們將使用一個java語言實現(xiàn)的雇員信息查詢系統(tǒng)查詢存儲在磁盤文件或者數(shù)據(jù)庫中的雇員人事檔案信息。作為一個用戶,我們完全有可能需要回頭去查看幾分鐘甚至幾秒鐘前查看過的雇員檔案信息(同樣,我們在瀏覽web頁面的時候也經(jīng)常會使用“后退”按鈕)。這時我們通常會有兩種程序?qū)崿F(xiàn)方式:一種是把過去查看過的雇員信息保存在內(nèi)存中,每一個存儲了雇員檔案信息的java對象的生命周期貫穿整個應用程序始終;另一種是當用戶開始查看其他雇員的檔案信息的時候,把存儲了當前所查看的雇員檔案信息的java對象結(jié)束引用,使得垃圾收集線程可以回收其所占用的內(nèi)存空間,當用戶再次需要瀏覽該雇員的檔案信息的時候,重新構(gòu)建該雇員的信息。很顯然,第一種實現(xiàn)方法將造成大量的內(nèi)存浪費,而第二種實現(xiàn)的缺陷在于即使垃圾收集線程還沒有進行垃圾收集,包含雇員檔案信息的對象仍然完好地保存在內(nèi)存中,應用程序也要重新構(gòu)建一個對象。我們知道,訪問磁盤文件、訪問網(wǎng)絡(luò)資源、查詢數(shù)據(jù)庫等操作都是影響應用程序執(zhí)行性能的重要因素,如果能重新獲取那些尚未被回收的java對象的引用,必將減少不必要的訪問,大大提高程序的運行速度。
2 如果使用軟引用
softreference的特點是它的一個實例保存對一個java對象的軟引用,該軟引用的存在不妨礙垃圾收集線程對該java對象的回收。也就是說,一旦softreference保存了對一個java對象的軟引用后,在垃圾線程對這個java對象回收前,softreference類所提供的get()方法返回java對象的強引用。另外,一旦垃圾線程回收該java對象之后,get()方法將返回null。看下面代碼:
1
2
|
myobject aref = new myobject(); softreference asoftref= new softreference(aref); |
此時,對于這個myobject對象,有兩個引用路徑,一個是來自softreference對象的軟引用,一個來自變量areference的強引用,所以這個myobject對象是強可及對象。
隨即,我們可以結(jié)束areference對這個myobject實例的強引用:
1
|
aref= null ; |
此后,這個myobject對象成為了軟可及對象。如果垃圾收集線程進行內(nèi)存垃圾收集,并不會因為有一個softreference對該對象的引用而始終保留該對象。java虛擬機的垃圾收集線程對軟可及對象和其他一般java對象進行了區(qū)別對待:軟可及對象的清理是由垃圾收集線程根據(jù)其特定算法按照內(nèi)存需求決定的。也就是說,垃圾收集線程會在虛擬機拋出outofmemoryerror之前回收軟可及對象,而且虛擬機會盡可能優(yōu)先回收長時間閑置不用的軟可及對象,對那些剛剛構(gòu)建的或剛剛使用過的“新”軟可反對象會被虛擬機盡可能保留。在回收這些對象之前,我們可以通過:
1
|
myobjectanotherref=(myobject)asoftref.get(); |
重新獲得對該實例的強引用。而回收之后,調(diào)用get()方法就只能得到null了。
3 使用referencequeue清除失去了軟引用對象的softreference作為一個java對象,softreference對象除了具有保存軟引用的特殊性之外,也具有java對象的一般性。所以,當軟可及對象被回收之后,雖然這個softreference對象的get()方法返回null,但這個softreference對象已經(jīng)不再具有存在的價值,需要一個適當?shù)那宄龣C制,避免大量softreference對象帶來的內(nèi)存泄漏。在java.lang.ref包里還提供了referencequeue。如果在創(chuàng)建softreference對象的時候,使用了一個referencequeue對象作為參數(shù)提供給softreference的構(gòu)造方法,如:
1
2
|
referencequeue queue = new referencequeue(); softreference ref= new softreference(amyobject, queue); |
那么當這個softreference所軟引用的amyohject被垃圾收集器回收的同時,ref所強引用的softreference對象被列入referencequeue。也就是說,referencequeue中保存的對象是reference對象,而且是已經(jīng)失去了它所軟引用的對象的reference對象。另外從referencequeue這個名字也可以看出,它是一個隊列,當我們調(diào)用它的poll()方法的時候,如果這個隊列中不是空隊列,那么將返回隊列前面的那個reference對象。
在任何時候,我們都可以調(diào)用referencequeue的poll()方法來檢查是否有它所關(guān)心的非強可及對象被回收。如果隊列為空,將返回一個null,否則該方法返回隊列中前面的一個reference對象。利用這個方法,我們可以檢查哪個softreference所軟引用的對象已經(jīng)被回收。于是我們可以把這些失去所軟引用的對象的softreference對象清除掉。常用的方式為:
1
2
3
4
5
6
7
|
softreference ref = null ; while ((ref = (employeeref) q.poll()) != null ) { // 清除ref } |
理解了referencequeue的工作機制之后,我們就可以開始構(gòu)造一個java對象的高速緩存器了。
4通過軟可及對象重獲方法實現(xiàn)java對象的高速緩存
利用java2平臺垃圾收集機制的特性以及前述的垃圾對象重獲方法,我們通過一個雇員信息查詢系統(tǒng)的小例子來說明如何構(gòu)建一種高速緩存器來避免重復構(gòu)建同一個對象帶來的性能損失。我們將一個雇員的檔案信息定義為一個employee類:
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
|
publicclass employee { private string id; // 雇員的標識號碼 private string name; // 雇員姓名 private string department; // 該雇員所在部門 private string phone; // 該雇員聯(lián)系電話 privateintsalary; // 該雇員薪資 private string origin; // 該雇員信息的來源 // 構(gòu)造方法 public employee(string id) { this .id = id; getdatafromlnfocenter(); } // 到數(shù)據(jù)庫中取得雇員信息 privatevoid getdatafromlnfocenter() { // 和數(shù)據(jù)庫建立連接井查詢該雇員的信息,將查詢結(jié)果賦值 // 給name,department,plone,salary等變量 // 同時將origin賦值為"from database" } …… |
這個employee類的構(gòu)造方法中我們可以預見,如果每次需要查詢一個雇員的信息。哪怕是幾秒中之前剛剛查詢過的,都要重新構(gòu)建一個實例,這是需要消耗很多時間的。下面是一個對employee對象進行緩存的緩存器的定義:
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
import java.lang.ref.referencequeue; import java.lang.ref.softreference; import java.util.hashtable; publicclass employeecache { staticprivate employeecache cache; // 一個cache實例 private hashtable<string,employeeref> employeerefs; // 用于chche內(nèi)容的存儲 private referencequeue<employee> q; // 垃圾reference的隊列 // 繼承softreference,使得每一個實例都具有可識別的標識。 // 并且該標識與其在hashmap內(nèi)的key相同。 privateclass employeeref extends softreference<employee> { private string _key = "" ; public employeeref(employee em, referencequeue<employee> q) { super (em, q); _key = em.getid(); } } // 構(gòu)建一個緩存器實例 private employeecache() { employeerefs = new hashtable<string,employeeref>(); q = new referencequeue<employee>(); } // 取得緩存器實例 publicstatic employeecache getinstance() { if (cache == null ) { cache = new employeecache(); } returncache; } // 以軟引用的方式對一個employee對象的實例進行引用并保存該引用 privatevoid cacheemployee(employee em) { cleancache(); // 清除垃圾引用 employeeref ref = new employeeref(em, q); employeerefs.put(em.getid(), ref); } // 依據(jù)所指定的id號,重新獲取相應employee對象的實例 public employee getemployee(string id) { employee em = null ; // 緩存中是否有該employee實例的軟引用,如果有,從軟引用中取得。 if (employeerefs.containskey(id)) { employeeref ref = (employeeref) employeerefs.get(id); em = (employee) ref.get(); } // 如果沒有軟引用,或者從軟引用中得到的實例是null,重新構(gòu)建一個實例, // 并保存對這個新建實例的軟引用 if (em == null ) { em = new employee(id); system.out.println( "retrieve from employeeinfocenter. id=" + id); this .cacheemployee(em); } return em; } // 清除那些所軟引用的employee對象已經(jīng)被回收的employeeref對象 privatevoid cleancache() { employeeref ref = null ; while ((ref = (employeeref) q.poll()) != null ) { employeerefs.remove(ref._key); } } // 清除cache內(nèi)的全部內(nèi)容 publicvoid clearcache() { cleancache(); employeerefs.clear(); system.gc(); system.runfinalization(); } } |
使用弱引用構(gòu)建非敏感數(shù)據(jù)的緩存
1全局 map 造成的內(nèi)存泄漏
無意識對象保留最常見的原因是使用map將元數(shù)據(jù)與臨時對象(transient object)相關(guān)聯(lián)。假定一個對象具有中等生命周期,比分配它的那個方法調(diào)用的生命周期長,但是比應用程序的生命周期短,如客戶機的套接字連接。需要將一些元數(shù)據(jù)與這個套接字關(guān)聯(lián),如生成連接的用戶的標識。在創(chuàng)建socket時是不知道這些信息的,并且不能將數(shù)據(jù)添加到socket對象上,因為不能控制 socket 類或者它的子類。這時,典型的方法就是在一個全局 map 中存儲這些信息,如下面的 socketmanager 類所示:使用一個全局 map 將元數(shù)據(jù)關(guān)聯(lián)到一個對象。
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
|
publicclass socketmanager { private map<socket, user> m = new hashmap<socket, user>(); publicvoid setuser(socket s, user u) { m.put(s, u); } public user getuser(socket s) { returnm.get(s); } publicvoid removeuser(socket s) { m.remove(s); } } |
這種方法的問題是元數(shù)據(jù)的生命周期需要與套接字的生命周期掛鉤,但是除非準確地知道什么時候程序不再需要這個套接字,并記住從 map 中刪除相應的映射,否則,socket 和 user 對象將會永遠留在 map 中,遠遠超過響應了請求和關(guān)閉套接字的時間。這會阻止 socket 和 user 對象被垃圾收集,即使應用程序不會再使用它們。這些對象留下來不受控制,很容易造成程序在長時間運行后內(nèi)存爆滿。除了最簡單的情況,在幾乎所有情況下找出什么時候 socket 不再被程序使用是一件很煩人和容易出錯的任務,需要人工對內(nèi)存進行管理。
2如何使用weakhashmap
在java集合中有一種特殊的map類型—weakhashmap,在這種map中存放了鍵對象的弱引用,當一個鍵對象被垃圾回收器回收時,那么相應的值對象的引用會從map中刪除。weakhashmap能夠節(jié)約存儲空間,可用來緩存那些非必須存在的數(shù)據(jù)。關(guān)于map接口的一般用法。
下面示例中mapcache類的main()方法創(chuàng)建了一個weakhashmap對象,它存放了一組key對象的弱引用,此外main()方法還創(chuàng)建了一個數(shù)組對象,它存放了部分key對象的強引用。
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
import java.util.weakhashmap; class element { private string ident; public element(string id) { ident = id; } public string tostring() { returnident; } publicint hashcode() { returnident.hashcode(); } publicboolean equals(object obj) { return obj instanceof element && ident.equals(((element) obj).ident); } protectedvoid finalize(){ system.out.println( "finalizing " +getclass().getsimplename()+ " " +ident); } } class key extends element{ public key(string id){ super (id); } } class value extends element{ public value (string id){ super (id); } } publicclass canonicalmapping { publicstaticvoid main(string[] args){ int size= 1000 ; key[] keys= new key[size]; weakhashmap<key,value> map= new weakhashmap<key,value>(); for ( int i= 0 ;i<size;i++){ key k= new key(integer.tostring(i)); value v= new value(integer.tostring(i)); if (i% 3 == 0 ) keys[i]=k; map.put(k, v); } system.gc(); } } |
從打印結(jié)果可以看出,當執(zhí)行system.gc()方法后,垃圾回收器只會回收那些僅僅持有弱引用的key對象。id可以被3整除的key對象持有強引用,因此不會被回收。
3 用 weakhashmap 堵住泄漏
在 socketmanager 中防止泄漏很容易,只要用 weakhashmap 代替 hashmap 就行了。(這里假定socketmanager不需要線程安全)。當映射的生命周期必須與鍵的生命周期聯(lián)系在一起時,可以使用這種方法。用weakhashmap修復 socketmanager。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
publicclass socketmanager { private map<socket,user> m = new weakhashmap<socket,user>(); publicvoid setuser(socket s, user u) { m.put(s, u); } public user getuser(socket s) { returnm.get(s); } } |
4 配合使用引用隊列
weakhashmap 用弱引用承載映射鍵,這使得應用程序不再使用鍵對象時它們可以被垃圾收集,get() 實現(xiàn)可以根據(jù) weakreference.get() 是否返回 null 來區(qū)分死的映射和活的映射。但是這只是防止 map 的內(nèi)存消耗在應用程序的生命周期中不斷增加所需要做的工作的一半,還需要做一些工作以便在鍵對象被收集后從 map 中刪除死項。否則,map 會充滿對應于死鍵的項。雖然這對于應用程序是不可見的,但是它仍然會造成應用程序耗盡內(nèi)存。
引用隊列是垃圾收集器向應用程序返回關(guān)于對象生命周期的信息的主要方法。弱引用有個構(gòu)造函數(shù)取引用隊列作為參數(shù)。如果用關(guān)聯(lián)的引用隊列創(chuàng)建弱引用,在弱引用對象成為 gc 候選對象時,這個引用對象就在引用清除后加入到引用隊列中(具體參考上文軟引用示例)。weakhashmap 有一個名為 expungestaleentries() 的私有方法,大多數(shù) map 操作中會調(diào)用它,它去掉引用隊列中所有失效的引用,并刪除關(guān)聯(lián)的映射。
uml:使用關(guān)聯(lián)類指明特定形式的引用
關(guān)聯(lián)類能夠用來指明特定形式的引用,如弱(weak)、軟(soft)或虛 (phantom)引用。
也可以如下的構(gòu)造型方式。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://www.cnblogs.com/huajiezh/p/5835618.html