何群芳+時招軍
摘 要:內存的泄漏與溢出是在進行Android開發時最常見且棘手的問題之一。為了提高Android開發的質量和效率,總結了Android的內存泄漏與溢出的常見類型和解決方法。內存泄漏的常見類型有集合類泄漏、傳入Activity的Context造成的內存泄漏、非靜態內部類創建靜態實例和線程造成的內存泄漏等;內存溢出的常見類型有由強引用造成的內存溢出、由大量圖片顯示導致的內存溢出、從數據庫中取出大量數據造成的內存溢出、代碼中存在死循環或循環產生過多重復對象實體造成的內存溢出等,并提出了由圖片造成的內存溢出的新的解決方法。
關鍵詞:內存管理;Android;內存泄漏;內存溢出
DOIDOI:10.11907/rjdk.172379
中圖分類號:TP301
文獻標識碼:A 文章編號:1672-7800(2018)002-0050-03
0 引言
Java的內存管理即為對象的分配和釋放問題。在Java中,程序員需要通過關鍵字new為每個對象申請內存空間(基本類型除外),所有對象都在堆(Heap)中分配空間。而對象的釋放由GC決定和執行。該方式確實簡化了程序員的工作,但同時也加大了JVM的工作量。因為GC為了能夠正確地釋放對象,必須監控每一個對象的運行狀態,包括對象的申請、引用、被引用、賦值等[1-4]。
在Java中,內存泄漏就是存在一些被分配的對象,這些對象存在以下兩個特點:首先,這些對象是可達的,即在有向圖中,存在通路可以與其相連(也即是說仍存在對該對象的引用);其次,這些對象是無用的,即程序以后不會再使用這些對象。如果滿足這兩個條件,這些對象即可判定為Java中的內存泄漏。內存泄漏造成的后果在于內存泄漏堆積,如果一直存在內存泄漏,最終會導致越來越多內存無法回收而又不能被利用,最終可用內存越來越少。內存溢出是程序占用的內存大小超過了虛擬機(DVM)的允許值,而造成內存溢出的最主要原因是高質量圖片的大量出現。通過對Java內存分配、內存管理和垃圾回收原理的理解,總結導致內存泄漏和內存溢出的常見原因,并提出相應預防措施,可有效提升程序質量。
1 內存泄漏與內存溢出常見類型與解決方法
內存泄漏(Memory Leak)是指程序中己動態分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重后果;內存溢出(Out of Memory)通俗理解就是內存不夠,通常在運行大型軟件或游戲時,軟件或游戲所需的內存遠遠超出了主機內安裝的內存所能承受的大小,稱為內存溢出。此時軟件或游戲無法運行,系統會提示內存溢出,有時甚至會自動關閉軟件,重啟電腦或軟件后釋放掉部分內存,又可以正常運行該軟件。
1.1 內存泄漏常見類型與解決方法
1.1.1 集合類泄漏
集合類如果僅有添加元素的方法,而沒有相應的刪除機制,可能導致內存被占用。如果該集合類是全局性的變量 (比如類中的靜態屬性、全局性的 map 等即有靜態引用或 final 一直指向它),則沒有相應的刪除機制,很可能導致集合占用的內存只增不減,這就是集合類泄漏。
解決方法:給集合類添加相應的刪除機制,最簡單的方法是直接將集合置為null。
1.1.2 傳入Activity的Context造成的內存泄漏
因為Activity的生命周期可能比引用該Activity的生命周期短,所以即使該Activity被銷毀,依然處于被引用狀態,GC無法回收,造成內存泄漏。例如在static的工具類中,在創建數據庫或打開數據庫、開啟系統服務與廣播時以及創建單例等情況下傳入Activity的Context,即使Activity被銷毀,GC依然無法回收該Activity所占用的資源,這就是由傳入Activity的Context造成的內存泄漏。
解決方法:如果需要傳入Context,可以改用Application的Context,即getApplicationContext()。當然,Application的Context也不是萬能的,基本上除了start a activity和show a dialog需要Activity的Context之外,其他的都可以用Application的Context替代。
1.1.3 非靜態內部類創建靜態實例與線程造成的內存泄漏
非靜態內部類默認持有外部類的引用,而該非靜態內部類又創建了一個靜態實例,該實例的生命周期和應用長度相同,將導致了該靜態實例一直會持有該Activity的引用,導致Activity的內存資源不能正常回收。線程和非靜態內部類相似。
解決方法:將非靜態的內部類改為靜態的內部類,并將對Activity的引用改為弱引用,使內部類和線程的生命周期脫離Activity的生命周期,并且當GC進行回收時,可以回收被弱引用的Activity等[5-8]。
1.2 內存溢出常見類型與解決方法
1.2.1 由強引用造成的內存溢出
若所有的引用都是強引用,則大量內存會被占用,最終導致內存溢出。
解決方法:使用弱引用或軟引用,軟引用的對象在內存不足時可被GC回收,弱引用的對象在垃圾回收時可被回收。
1.2.2 由大量圖片顯示導致的內存溢出
為解決由大量圖片顯示造成的內存溢出,可以使用BitmapFactory.Options類,在返回參數時,只返回Bitmap的尺寸大小,而不將其加載到內存中,可有效減少內存溢出。同時在加載完后調用system.gc()通知系統及時回收。
1.2.3 從數據庫中取出大量數據造成的內存溢出endprint
檢查在數據庫查詢中,是否有一次獲得全部數據的查詢。一般而言,如果一次取十萬條記錄到內存,就可能引起內存溢出。該問題比較隱蔽,在上線前,數據庫中數據較少,通常運行正常,上線后,數據庫中數據增多,一次查詢即有可能引起內存溢出。因此,對于數據庫查詢,盡量采用分頁的方式查詢。
1.2.4 代碼中存在死循環或循環產生過多重復對象實體造成的內存溢出
出現這種情況,只能通過查看日志找出產生該問題的原因,檢查代碼中是否有死循環、遞歸調用,或大循環重復產生的新對象實體。
1.2.5 內存分配大小不夠
虛擬機默認的內存大小對于大程序來說可能根本不夠用,這時則需要手動更改默認的內存大小。對于Android平臺而言,其托管層使用的Dalvik JavaVM從目前的表現看還有很多地方可以進行優化處理,比如在開發一些大型游戲或比較消耗資源的應用中可能考慮手動干涉GC處理,如使用 dalvik.system.VMRuntime類提供的setTargetHeapUtilization方法可以增強程序堆內存的處理效率[9-11]。具體原理可以參考開源工程,使用方法代碼如下:
Private final static float HEAP_UTILIZATION=0.6f;
//在程序onCreate時調用
VMRuntime.getRuntime().setTargetHeapUtilization(HEAP_UTILIZATION);
//自定義堆內存大小,如:
Private final static int HEAP_SIZE=8*1024*1024;
//設置最小堆內存大小為8MB
VMRuntime.getRuntime().setMininumHeapSize(HEAP_SIZE);
2 由圖片造成內存溢出的新解決方法與特點
2.1 解決方法
對于Android程序而言,大量高質量圖片的顯示幾乎無法避免,這也是造成內存溢出最常見的原因之一。常見的解決方法是由程序員考慮到每一個可能造成內存溢出的情況,然后加以防范。但是若使用新組件RecyclerView,并改用RecyclerView.Adpter,則可以將由圖片造成的內存溢出交由RecyclerView類解決。RecyclerView類主要關注的是資源的回收與重用。使用RecyclerView.Adpter的關鍵代碼如下:
public class SimpleAdapter extends RecyclerView.Adapter
/**
使用RecyclerView.Adapter重點在于重載這兩個方法,第一個方法負責創建ViewHolder,第二個方法負責顯示ViewHolder。只要將ViewHolder創建好,剩下資源的回收和重用RecyclerView會自動解決。
*/
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
}
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
}
另外,對于服務器上的圖片,Android端可以直接使用路徑訪問,不需要從服務器端下載到客戶端再進行顯示。即使要緩存到Android本地,也可以直接保存路徑。
2.2 特點
使用該方法,不僅可以讓同一個fragment中的內容類型不一樣,使單個頁面的內容多元化,最主要的是RecyclerView不關心視圖問題,中心放在回收和重用上。由RecyclerView直接進行資源的回收和利用,使程序員不需要再考慮是否會發生內存溢出,節省程序員在該方面花費的時間,提高程序編寫效率。
3 結語
內存泄漏和內存溢出是Android開發中最為常見且繁瑣的問題之一,在實際開發過程中想要徹底根除這兩個問題幾乎不可能,只能在編寫代碼時考慮全面,提前作好防范,養成良好的編程習慣,從而達到有效防止內存泄露和內存溢出的效果。通過對內存泄漏和內存溢出常見類型和解決方法的合理總結和歸納,可有效解決Android開發中遇到的一系列難點。
對內存的管理,關鍵在于資源的及時回收以及對內存泄漏的預防。內存的回收主要是對圖片等其他對象的回收與利用。其中預防內存泄漏最主要的方法是將對Activity的Context的傳入改為對Application的Context的傳入,因為在很多UI界面以及廣播等的使用過程中幾乎都需要用到Context,而如果用Activity的Context,那么幾乎整個應用程序中都會遍布Activity的Context,從而導致Activity無法成功銷毀,最終導致內存泄漏。所以在需要傳入Context時,一定要謹慎思考其可行性。另一種預防內存泄漏的方法是將對Activity的強引用改為弱引用或軟引用,使GC在回收時,能成功回收該Activity所占用的內存資源。
參考文獻:
[1] 周志明.深入理解Java虛擬機:JVM高級特性與最佳實踐[M].第2版.北京:機械工業出版社,2013:50-98.
[2] 張秀宏.自己動手寫Java虛擬機[M].北京:機械工業出版社,2016:60-98.
[3] 葛一鳴.實戰Java虛擬機—JVM故障診斷與性能優化[M].北京:電子工業出版社,2015:30-45.
[4] 高翔龍.Java虛擬機精講[M].北京:電子工業出版社,2015:88-98.
[5] 羅彧成.Android應用性能優化最佳實踐[M].北京:機械工業出版社,2017:70-110.
[6] 埃爾韋.Android應用性能優化[M].白龍,譯.北京:人民郵電出版社,2012:10-50.
[7] DOUG SILLARS.高性能Android應用開發[M].王若蘭,周丹紅,譯.北京:人民郵電出版社,2016:98-119.
[8] 騰訊SNG專項測試團隊.Android移動性能實戰[M].北京:電子工業出版社,2017: 50-152.
[9] 鐘世禮.深入解析Android虛擬機[M].北京:人民郵電出版社,2016:40-48.
[10] 張國印,吳艷霞. AndroidDalvik虛擬機結構及機制剖析[M].北京:清華大學出版社,2014:1-20.
[11] 張子言.深入解析Android虛擬機[M].北京:清華大學出版社,2014:50-90.