生活随笔
收集整理的這篇文章主要介紹了
垃圾回收机制与引用类型
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
http://www.devdiv.com/Android-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E6%9C%BA%E5%88%B6%E4%B8%8E%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B-thread-130475-1-1.html
Java語言的一個重要特性是引入了自動的內存管理機制,這樣一來,開發人員就不需要自己來管理應用中的內存了。C/C++開發人員需要通過malloc/free和new/delete等函數來顯式的分配和釋放內存。這對開發人員提出了比較高的要求,因為這些方法使用不當很容易造成內存訪問錯誤和內存泄露等嚴重問題。一個最常見的問題是 “懸掛引用(dangling references)”,即一個引用或指針所指向的內存區塊已經被錯誤的回收并重新分配給新的對象了,如果程序繼續使用這個引用或指針的話會,必然會造成不可預期的結果。開發人員有可能忘記顯式地調用內存釋放的函數而造成內存泄露。而自動的內存管理則是把管理內存的任務交給編程語言的運行環境來負責。開發人員并不需要關心內存的分配與回收的底層細節。Java平臺通過垃圾回收器(GC)來進行自動的內存管理。
一、JAVA的垃圾回收機制
? ?? ???Java的垃圾回收器要負責完成三件任務:
? ?? ???1、分配內存
? ?? ???2、確保被引用的對象的內存不被錯誤回收
? ?? ???3、回收不再被引用的對象的內存空間
? ?? ???垃圾回收是一個復雜而且耗時的操作,如果JVM(Java虛擬機)花費過多的時間在垃圾回收上,則勢必會影響應用的運行性能。一般情況下,當垃圾回收器在進行回收操作的時候,整個應用的執行是被暫時中止(stop-the-world)的。這是因為垃圾回收器需要更新應用中所有對象引用的實際內存地址。
? ?? ???不同的硬件平臺所能支持的垃圾回收方式也不同。比如在多CPU的平臺上,就可以通過并行的方式來回收垃圾。而單CPU平臺則只能串行進行。不同的應用所期望的垃圾回收方式也會有所不同。服務器端應用可能希望在應用的整個運行時間中,花在垃圾回收上的時間總數越小越好。而對于與用戶交互的應用來說,則可能希望所垃圾回收所帶來的應用停頓的時間間隔越小越好。對于不同的情況,JVM(Java虛擬機)中提供了多種垃圾回收方法以及對應的性能調優參數,應用可以根據需要來進行定制。
? ?? ???Java 垃圾回收機制最基本的做法是分代回收。內存區域被劃分成不同的代,對象根據其存活的時間被保存在對應代的區域中。一般的實現是劃分成3個代:年輕、年老和永久。內存的分配是發生在年輕代中。當一個對象存活時間足夠長的時候,它就會被復制到年老代中。對于不同的代可以使用不同的垃圾回收算法。進行分代回收的出發點是對應用中對象存活時間進行研究之后得出的統計規律。一般來說,一個應用中的大部分對象的存活時間都很短。比如局部變量的存活時間就只在方法的執行過程中。基于這一點,對于年輕代的垃圾回收算法就可以很有針對性。
? ?? ???年輕代的內存區域被進一步劃分成Eden區和兩個Survivor區(存活區)。Eden區是進行內存分配的地方,是一塊連續的空閑內存區域。在上面的進行內存分配速度非常快,因為不需要進行可用內存塊的查找。兩個Survivor區中始終有一個是空白的。在進行垃圾回收的時候,Eden區和其中一個非空Survivor區中還存活的對象,根據其存活時間被復制到當前空白的Survivor區或年老代中。經過這一次的復制之后,之前非空的Survivor區中包含了當前還存活的對象,而Eden區和另一個Survivor區中的內容已經不再需要了,只需要簡單地把這兩個區域清空即可。下一次垃圾回收的時候,這兩個Survivor區的角色就發生了交換。一般來說,年輕代區域較小,而且大部分對象都已經不再存活,因此在其中查找存活對象的效率較高。
? ?? ???而對于年老和永久代的內存區域,則采用的是不同的回收算法,稱為“標記-清除-壓縮(Mark-Sweep-Compact)”法。標記的過程是找出當前還存活的對象,并進行標記;清除則是遍歷整個內存區域,找出其中需要進行回收的區域;而壓縮則是把存活對象的內存移動到整個內存區域的一端,使得另一端是一塊連續的空閑區域,方便進行內存分配和復制。
? ?? ???Java中提供了4種不同的垃圾回收機制。最常用的是串行回收方式,即使用單個CPU回收年輕和年老代的內存。在回收的過程中,應用程序被暫時中止。回收方式使用的是上面提到的最基本的分代回收。串行回收方式適合于一般的單CPU桌面平臺。如果是多CPU的平臺,則適合的是并行回收方式。這種方式在對年輕代進行回收的時候,會使用多個CPU來并行處理,可以提升回收的性能。并發標記-清除回收方式適合于對應用的響應時間要求比較高的情況,即需要減少垃圾回收所帶來的應用暫時中止的時間。這種做法的優點在于可以在應用運行的同時標記存活對象與回收垃圾,而只需要暫時中止應用比較短的時間。
二、JAVA引用類型
? ?? ???如果一個內存中的對象沒有任何引用的話,就說明這個對象已經不再被使用了,從而可以成為被垃圾回收的候選。不過,由于垃圾回收器的運行時間不確定,所以可被垃圾回收的對象的實際被回收時間是不確定的。對于一個對象來說,只要有引用的存在,它就會一直存在于內存中。如果這樣的對象越來越多,超出了JVM中的內存總數,JVM就會拋出OutOfMemory錯誤。
? ?? ???雖然垃圾回收的具體運行是由JVM來控制的,但是開發人員仍然可以在一定程度上與垃圾回收器進行交互,其目的在于更好的幫助垃圾回收器管理好應用的內存。
? ?? ???在Java中把對象的引用分為 4 種級別,從而使程序能更加靈活地控制對象的生命周期。這 4 種級別由高到低依次為:強引用、軟引用、弱引用和虛引用。
? ?? ???JAVA對象引用類層次結構圖如下圖所示:
圖14-1??JAVA對象引用類層次結構圖
? ?? ???1)強引用類型
? ?? ???在一般的Java程序中,見到最多的就是強引用(strong reference)。如我們最常見的代碼
| 2 | String str = “Hello Wolrd”; |
| 3 | ???? ?? ?? ?? ??Object obj =?new?Object(); |
| 4 | ???? ?? ?? ?? ??Date date =?new?Date(), |
? ?? ???在上面的代碼中str、obj、date都是對象的強引用。對象的強引用可以在程序中到處傳遞。很多情況下,會同時有多個引用指向同一個對象。強引用的存在限制了對象在內存中的存活時間。假如對象A中包含了一個對象B的強引用,那么一般情況下,對象B的存活時間就不會短于對象A。如果對象A沒有顯式的把對象B的引用設為null的話,就只有當對象A被垃圾回收之后,對象B才不再有引用指向它,才可能獲得被垃圾回收的機會。
? ?? ???2)軟引用類型
? ?? ???軟引用(soft reference)在強度上弱于強引用,通過SoftReference類來表示。它的作用是告訴垃圾回收器,程序中的哪些對象并不那么重要,當內存不足的時候是可以被暫時回收的。當JVM中的內存不足的時候,垃圾回收器會釋放那些只被軟引用所指向的對象。如果全部釋放完這些對象之后,內存還不足,才會拋出Out Of Memory錯誤。
? ?? ???軟引用非常適合于創建緩存。當系統內存不足的時候,緩存中的內容是可以被釋放的。比如考慮一個圖像編輯器的程序,該程序會把圖像文件的全部內容都讀取到內存中,以方便進行處理,而用戶也可以同時打開多個文件。當同時打開的文件過多的時候,就可能造成內存不足。如果使用軟引用來指向圖像文件內容的話,垃圾回收器就可以在必要的時候回收掉這些內存。
? ?? ???從下面的Java代碼中可以看到軟引用類型的使用方法。
| 01 | ???? ???public?class?BitmapCache { |
| 02 | ???? ?? ?? ?? ?? ???private?String url;//圖片URL |
| 03 | ???? ?? ?? ?? ?? ???private?SoftReference<Bitmap> softRef;// //軟引用-只有當系統內存不足的時候才去釋放 |
| 04 | ???? ?? ?? ?? ??public?BitmapCache (String url) { |
| 05 | ???? ?? ?? ?? ?? ?? ?? ?this. url = url; |
| 06 | ???? ?? ?? ?? ?? ?? ?? ??softRef =?new?SoftReference< Bitmap >(null); |
| 08 | ???? ?? ?? ?? ?? ??private?Bitmap loadRemoteBitmap() { |
| 09 | ???? ?? ?? ?? ?? ?? ?? ?final?DefaultHttpClient client =?new?DefaultHttpClient(); |
| 10 | ???? ?? ?? ?? ?? ?? ?? ?final?HttpGet getRequest =?new?HttpGet(url); |
| 11 | ???? ?? ?? ?? ?? ?? ?? ?HttpResponse response = client.execute(getRequest); |
| 12 | ???? ?? ?? ?? ?? ?? ?? ?final?int?statusCode = response.getStatusLine().getStatusCode(); |
| 13 | ???? ?? ?? ?? ?? ?? ?? ?final?HttpEntity entity = response.getEntity(); |
| 14 | ???? ?? ?? ?? ?? ?? ?? ?InputStream inputStream = entity.getContent(); |
| 15 | ???? ?? ?? ?? ?? ?? ?? ?final?ByteArrayOutputStream dataStream =?newByteArrayOutputStream(); |
| 16 | ???? ?? ?? ?? ?? ?? ?? ?OutputStream outputStream =?newBufferedOutputStream(dataStream, IO_BUFFER_SIZE); |
| 17 | ???? ?? ?? ?? ?? ?? ?? ?copy(inputStream, outputStream); |
| 18 | ???? ?? ?? ?? ?? ?? ?? ?outputStream.flush(); |
| 19 | ???? ?? ?? ?? ?? ?? ?? ?final?byte[] data = dataStream.toByteArray(); |
| 20 | ???? ?? ?? ?? ?? ?? ?? ?final?Bitmap bitmap = BitmapFactory.decodeByteArray(data,0,??data.length); |
| 21 | ???? ?? ?? ?? ?? ?? ???return?bitmap; |
| 23 | ???? ?? ?? ?? ?? ??public?Bitmap get Bitmap () { |
| 24 | ???? ?? ?? ?? ?? ?? ?? ?Bitmap bitmap = softRef.get(); |
| 25 | ???? ?? ?? ?? ?? ?? ???if?(bitmap ==?null) {//系統內存不足的時,圖片已經被釋放需要重新加載網絡圖片 |
| 26 | ???? ?? ?? ?? ?? ?? ?? ?? ?bitmap = loadRemoteBitmap (); |
| 27 | ???? ?? ?? ?? ?? ?? ?? ?? ??softRef =?new?SoftReference< Bitmap >( bitmap); |
| 28 | ???? ?? ?? ?? ?? ?? ??} |
| 29 | ???? ?? ?? ?? ?? ?? ??return?bitmap; |
? ?? ???在使用上面程序的時候,由于軟引用所指向的對象可能被回收掉,在通過get方法來獲取軟引用所實際指向的對象的時候,總是要檢查該對象是否還存活。
? ?? ???3)弱引用類型
? ?? ???弱引用(weak reference)在強度上弱于軟引用,通過WeakReference類來表示。它的作用是引用一個對象,但是并不阻止該對象被回收。如果使用一個強引用的話,只要該引用存在,那么被引用的對象是不能被回收的,弱引用則沒有這個問題。在垃圾回收器運行的時候,如果一個對象的所有引用都是弱引用的話,該對象會被回收。弱引用的作用在于解決強引用所帶來的對象之間在存活時間上的耦合關系。
? ?? ???弱引用最常見的用處是在集合類中,尤其在哈希表中。哈希表的接口允許使用任何Java對象作為鍵(Key)。當一個鍵值對(Key-Value)被放入到哈希表中之后,哈希表對象本身就有了對這些鍵和值對象的引用。如果這種引用是強引用的話,那么只要哈希表對象本身還存活,其中所包含的鍵和值對象是不會被回收的。如果某個存活時間很長的哈希表中包含的鍵值對很多,最終就有可能消耗掉JVM中全部的內存。對于這種情況的解決辦法就是使用弱引用來引用這些對象,這樣哈希表中的鍵和值對象都能被垃圾回收器及時回收。在Java中可以使用WeakHashMap類來滿足這一常見需求。
? ?? ???4)虛引用類型
? ?? ???在介紹虛引用之前,先要了解一下Java提供的對象終止化機制(finalization)。
? ?? ???在Object類里面有個finalize方法,其設計的初衷是在一個對象被真正回收之前,可以用來執行一些清理的工作。因為Java并沒有提供類似C++的析構函數一樣的機制,只是簡單地通過 finalize方法來實現。但是問題在于垃圾回收器的運行時間是不固定的,所以這些清理工作的實際運行時間也是不能預知的。
? ?? ???使用虛引用(phantom reference)可以解決這個問題。在創建虛引用PhantomReference的時候必須要指定一個引用隊列。當一個對象的finalize方法已經被調用了之后,這個對象的虛引用會被加入到隊列中。通過檢查該隊列里面的內容就知道一個對象是不是已經準備要被回收了。
? ?? ???虛引用及其隊列的使用情況并不多見,主要用來實現比較精細的內存使用控制,這對于移動設備來說是很有意義的。程序可以在確定一個對象要被回收之后,再申請內存創建新的對象。通過這種方式可以使得程序所消耗的內存維持在一個相對較低的數量。
? ?? ???比如下面的Java代碼給出了一個緩沖區的實現示例。
| 02 | public?class?PhantomBuffer { |
| 04 | private?byte[] data =?new?byte[0]; |
| 06 | private?ReferenceQueue<byte[]> queue =?new?ReferenceQueue<byte[]>(); |
| 08 | private?PhantomReference<byte[]> ref =?new?PhantomReference<byte[]>(data, queue); |
| 10 | public?byte[] get(int?size) { |
| 11 | ???? ?? ?? ?//校驗size是否合法 |
| 12 | ???? ???if?(size <=?0) { |
| 13 | ???? ?? ?? ?// size不合法,拋出異常 |
| 14 | ???? ?? ?? ?throw?new?IllegalArgumentException("Wrong buffer size"); |
| 16 | ???? ?? ?? ?//檢查當前緩沖區是否能滿足申請的緩沖區大小 |
| 17 | ???? ???if?(data.length < size) { |
| 18 | ???? ?? ?? ?data =?null; |
| 19 | ???? ?? ?? ?? ?? ??//強制運行垃圾回收器 |
| 20 | ???? ?? ?? ?System.gc(); |
| 22 | ???? ?? ?? ?? ?? ?? ?? ???//該方法會阻塞直到隊列非空 |
| 23 | ???? ?? ?? ?? ??queue.remove(); |
| 24 | ???? ?? ?? ?? ?? ?? ?? ???//虛引用不會自動清空,要手動運行 |
| 25 | ???? ?? ?? ?? ??ref.clear(); |
| 26 | ???? ?? ?? ?? ??ref =?null; |
| 27 | ???? ?? ?? ?? ?? ?? ?? ???//創建新的緩沖區 |
| 28 | ???? ?? ?? ?? ??data =?new?byte[size]; |
| 29 | ???? ?? ?? ?? ?? ?? ?? ???//創建虛引用,并加入到應用隊列 |
| 30 | ???? ?? ?? ?? ??ref =?new?PhantomReference<byte[]>(data, queue); |
| 31 | ???? ?? ?? ?}?catch?(InterruptedException e) { |
| 32 | ???? ?? ?? ?? ??e.printStackTrace(); |
? ?? ???在上面的代碼中,每次申請新的緩沖區的時候,都首先確保之前的緩沖區的字節數組已經被成功回收。引用隊列的remove方法會阻塞直到新的虛引用被加入到隊列中。需要注意的是,這種做法會導致垃圾回收器被運行的次數過多,可能會造成程序的吞吐量過低。
? ?? ???5)引用隊列
? ?? ???在有些情況下,程序會需要在一個對象的可達到性發生變化的時候得到通知。比如某個對象的強引用都已經不存在了,只剩下軟引用或是弱引用。但是還需要對引用本身做一些的處理。典型的情景是在哈希表中,引用對象是作為WeakHashMap中的鍵對象的,當其引用的實際對象被垃圾回收之后,就需要把該鍵值對從哈希表中刪除。有了引用隊列(ReferenceQueue),就可以方便的獲取到這些弱引用對象,將它們從表中刪除。在軟引用和弱引用對象被添加到隊列之前,其對實際對象的引用會被自動清空。通過引用隊列的poll/remove方法就可以分別以非阻塞和阻塞的方式獲取隊列中的引用對象。
總結
以上是生活随笔為你收集整理的垃圾回收机制与引用类型的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。