Android 優化二 Java內存分配機製及內存泄漏
Java內存分配機製及內存泄漏
目錄介紹
- 1.JVM內存管理
- 1.1 JVM內存管理圖
- 1.2 Java采用GC進行內存管理。
- 2.JVM內存分配的幾種策略
- 2.1 靜態的
- 2.2 棧式的
- 2.3 堆式的
- 2.4 堆和棧的區別
- 2.5 得出結論
- 2.6 舉個例子
- 2.7 調用 System.gc();進行內存回收
- 3.GC簡單介紹
- 3.1 內存垃圾回收機製
- 3.2 關於GC介紹
- 3.3 如何監聽GC過程
- 3.4 GC過程與對象的引用類型關係
- 4.內存泄漏簡單介紹
- 4.1 內存泄漏的定義
- 4.2 內存泄漏與內存溢出的區別
- 4.3 內存泄漏帶來的影響
- 4.4 典型內存泄漏案例
關於內存泄漏筆記
0.本人寫的綜合案例
思維導圖
1.JVM內存管理
1.2 Java采用GC進行內存管理。
- Android虛擬機的垃圾回收采用的是根搜索算法。GC會從根節點(GC Roots)開始對heap進行遍曆。到最後,部分沒有直接或者間接引用到GC Roots的就是需要回收的垃圾,會被GC回收掉。而內存泄漏出現的原因就是存在了無效的引用,導致本來需要被GC的對象沒有被回收掉。 深入的JVM內存管理知識,推薦《深入理解Java虛擬機》。
2.JVM內存分配的幾種策略。
2.1 靜態的
- 靜態的存儲區,內存在程序編譯的時候就已經分配好了,這塊內存在程序整個運行期間都一直存在 它主要存放靜態數據、全局的static數據和一些常量。
2.2 棧式的
- 在執行方法時,方法一些內部變量的存儲都可以放在棧上麵創建,方法執行結束的時候這些存儲單元就會自動被注釋掉。棧 內存包括分配的運算速度很快,因為內在在處理器裏麵。當然容量有限,並且棧式一塊連續的內存區域,大小是由操作係統決定的,他先進後出,進出完成不會產生碎片,運行效率高且穩定
2.3 堆式的
- 也叫動態內存 。我們通常使用new 來申請分配一個內存。這裏也是我們討論內存泄漏優化的關鍵存儲區。GC會根據內存的使用情況,對堆內存裏的垃圾內存進行回收。堆內存是一塊不連續的內存區域,如果頻繁地new/remove會造成大量的內存碎片,GC頻繁的回收,導致內存抖動,這也會消耗我們應用的性能
2.4 堆和棧的區別
- 在函數中(說明是局部變量)定義的一些基本類型的變量和對象的引用變量都是在函數的棧內存中分配。
- 當在一段代碼塊中定義一個變量時,java就在棧中為這個變量分配內存空間,當超過變量的作用域後,java會自動釋放掉為該變量分配的內存空間,該內存空間可以立刻被另作他用。
- 堆內存用於存放所有由new創建的對象(內容包括該對象其中的所有成員變量)和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。
- 在堆中產生了一個數組或者對象後,還可以在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,以後就可以在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量相當於為數組或者對象起的一個別名,或者代號。
2.5 得出結論
- 1.局部變量的基本數據類型和引用,存儲於棧中,引用的對象實體存儲於堆中。因為它們屬於方法中的變量,生命周期隨方法而結束。 2.成員變量全部存儲與堆中(包括基本數據類型,引用和引用的對象實體),因為它們屬於類,類對象終究是要被new出來使用的。 3.我們所說的內存泄露,隻針對堆內存,他們存放的就是引用指向的對象實體。
2.6 舉個例子
public class Sample() {
int s1 = 0;
Sample mSample1 = new Sample();
public void method() {
int s2 = 1;
Sample mSample2 = new Sample();
}
}
Sample mSample3 = new Sample();
Sample 類的局部變量 s2 和引用變量 mSample2 都是存在於棧中,但 mSample2 指向的對象是存在於堆上的。
mSample3 指向的對象實體存放在堆上,包括這個對象的所有成員變量 s1 和 mSample1,而它自己存在於棧中。
2.7 調用 System.gc();進行內存回收
- 我們知道可以調用 System.gc();進行內存回收,但是GC不一定會執行。麵對GC的機製,我們是否無能為力?其實我們可以通過聲明一些引用標記來讓GC更好對內存進行回收。
- 小技巧 成員變量全部存儲在堆中(包括基本數據類型,引用及引用的對象實體),因為他們屬於類,類對象最終還是要被new出來的 局部變量的基本數據類型和引用存在棧中,應用的對象實體存儲在堆中。因為它們屬於方法當中的變量,生命周期會隨著方法一起結束
3.GC工作原理
3.1 內存垃圾回收機製
- 是從程序的主要運行對象(如靜態對象/寄存器/棧上指向的堆內存對象等)開始檢查引用鏈,當遍曆一遍後得到上述這些無法回收的對象和他們所引用的對象鏈,組成無法回收的對象集合,而其他孤立對象(集)就作為垃圾回收
- GC為了能夠正確釋放對象,必須監控每一個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC都需要進行監控。監視對象狀態是為了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象不再被引用。
3.2 關於GC介紹
- 有幾個函數可以訪問GC,例如運行GC的函數System.gc(),但是根據Java語言規範定義,該函數不保證JVM的垃圾收集器一定會執行。因為不同的JVM實現者可能使用不同的算法管理GC
- 通常GC的線程的優先級別較低。JVM調用GC的策略也有很多種,有的是內存使用到達一定程度時,GC才開始工作,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。但通常來說,我們不需要關心這些。
- 通過關鍵字 new 為每個對象申請內存空間 (基本類型除外),所有的對象都在堆 (Heap)中分配空間
3.3 如何監聽GC過程
- 係統每進行一次GC操作時,都會在LogCat中打印一條日誌,我們隻要去分析這條日誌就可以了,日誌的基本格式如下
D/dalvikvm: , ,
第一部分GC_Reason,這個是觸發這次GC操作的
原因,一般情況下一共有以下幾種觸發GC操作的原因: - GC_CONCURRENT: 當我們應用程序的堆內存快要滿的時候,係統會自動觸發GC操作來釋放內存。 - GC_FOR_MALLOC: 當我們的應用程序需要分配更多內存,可是現有內存已經不足的時候,係統會進行GC操作來釋放內存。 - GC_HPROF_DUMP_HEAP: 當生成HPROF文件的時候,係統會進行GC操作,關於HPROF文件我們下麵會講到。 - GC_EXPLICIT: 這種情況就是我們剛才提到過的,主動通知係統去進行GC操作,比如調用System.gc()方法來通知係統。或者在DDMS中,通過工具按鈕也是可以顯式地告訴係統進行GC操作的。
第二部分Amount_freed,表示係統通過這次GC操作釋放了多少內存 第三部分Heap_stats中會顯示當前內存的空閑比例以及使用情況(活動對象所占內存 / 當前程序總內存) 第四部分Pause_time表示這次GC操作導致應用程序暫停的時間。關於這個暫停的時間,Android在2.3的版本當中進行過一次優化,在2.3之前GC操作是不能並發進行的,也就是係統正在進行GC,那麼應用程序就隻能阻塞住等待GC結束。雖說這個阻塞的過程並不會很長,也就是幾百毫秒,但是用戶在使用我們的程序時還是有可能會感覺到略微的卡頓。而自2.3之後,GC操作改成了並發的方式進行,就是說GC的過程中不會影響到應用程序的正常運行,但是在GC操作的開始和結束的時候會短暫阻塞一段時間,不過優化到這種程度,用戶已經是完全無法察覺到了
3.4 GC過程與對象的引用類型關係
- Java對引用的分類Strong reference, SoftReference, WeakReference, PhatomReference
軟引用和弱引用
在Android應用的開發中,為了防止內存溢出,在處理一些占用內存大而且聲明周期較長的對象時候,可以盡量應用軟引用和弱引用技術
軟/弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
利用這個隊列可以得知被回收的軟/弱引用的對象列表,從而為緩衝器清除已失效的軟/弱引用。內存泄漏的原因:
堆內存中的長生命周期的對象持有短生命周期對象的強/軟引用,盡管短生命周期對象已經不再需要,但是因為長生命周期對象持有它的引用而導致不能被回收,這就是Java中內存泄露的根本原因
4.內存泄漏簡單介紹
4.1 內存泄漏的定義
- 當一個對象已經不需要使用了,本該被回收時,而有另外一個正在使用的對象持有它的引用,從而導致了對象不能被GC回收。這種導致了本該被回收的對象不能被回收而停留在堆內存中,就產生了內存泄漏
4.2 內存泄漏與內存溢出的區別
內存泄漏(Memory Leak)
進程中某些對象已經沒有使用的價值了,但是他們卻還可以直接或間接地被引用到GC Root導致無法回收。當內存泄漏過多的時候,再加上應用本身占用的內存,日積月累最終就會導致內存溢出OOM內存溢出(OOM)
當應用的heap資源超過了Dalvik虛擬機分配的內存就會內存溢出
4.3 內存泄漏帶來的影響
應用卡頓
泄漏的內存影響了GC的內存分配,過多的內存泄漏會影響應用的執行效率應用異常(OOM)
過多的內存泄漏,最終會導致 Dalvik分配的內存,出現OOM
4.4 典型內存泄漏案例
- 案例代碼
Vector v = new Vector(10); for (int i = 1; i < 100; i++) { Object o = new Object(); v.add(o); o = null; }
- 分析 在這個例子中,我們循環申請Object對象,並將所申請的對象放入一個 Vector 中,如果我們僅僅釋放引用本身,那麼 Vector 仍然引用該對象,所以這個對象對 GC 來說是不可回收的。因此,如果對象加入到Vector 後,還必須從 Vector 中刪除,最簡單的方法就是將 Vector 對象設置為 null。
其他說明
- 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
- 領英:https://www.linkedin.com/in/chong-yang-049216146/
- 簡書:https://www.jianshu.com/u/b7b2c6ed9284
- csdn:https://my.csdn.net/m0_37700275
- 網易博客:https://yangchong211.blog.163.com/
- 新浪博客:https://blog.sina.com.cn/786041010yc
- github:https://github.com/yangchong211
- 喜馬拉雅聽書:https://www.ximalaya.com/zhubo/71989305/
- 脈脈:yc930211
- 360圖書館:https://www.360doc.com/myfiles.aspx
- 開源中國:https://my.oschina.net/zbj1618/blog
- 泡在網上的日子:https://www.jcodecraeer.com/member/content_list.php?channelid=1
- 郵箱:yangchong211@163.com
- 阿裏雲博客:https://yq.aliyun.com/users/article?spm=5176.100239.headeruserinfo.3.dT4bcV
最後更新:2017-11-01 21:05:04