引言
接App優(yōu)化之內(nèi)存優(yōu)化(序), 作為App優(yōu)化系列中內(nèi)存優(yōu)化的一個(gè)小部分.
由于內(nèi)存相關(guān)知識(shí)比較生澀, 內(nèi)存優(yōu)化中使用到的相關(guān)工具, 也有很多專有名詞. 對(duì)Java內(nèi)存管理, GC, Android內(nèi)存管理, Dalvik/ART等知識(shí)有一個(gè)理論的認(rèn)識(shí), 可以讓我們更好的使用這些工具, 分析內(nèi)存問(wèn)題.
據(jù)此, 我們就先從理論入手, 聊聊GC那些事兒.
1, 何為GC
GC 是 garbage collection 的縮寫(xiě), 垃圾回收的意思. 也可以是 Garbage Collector, 也就是垃圾回收器.
1.1 垃圾回收器
我們先來(lái)解釋下Garbage Collector(垃圾回收器).
內(nèi)存管理, 一直是編程中的一個(gè)大的問(wèn)題. 在較老的語(yǔ)言中, 例如C++語(yǔ)言中, 內(nèi)存管理是顯式的, 也就是說(shuō)使用者自己申請(qǐng)內(nèi)存使用, 自己釋放內(nèi)存. 這就是為什么C++語(yǔ)言中除了構(gòu)造函數(shù), 還有析構(gòu)函數(shù). 我們?cè)趧?chuàng)建對(duì)象的時(shí)候調(diào)用構(gòu)造函數(shù)創(chuàng)建, 系統(tǒng)會(huì)在對(duì)象結(jié)束其作用域的時(shí)候調(diào)用析構(gòu)函數(shù), 我們需要做的就是在析構(gòu)函數(shù)中釋放掉我們申請(qǐng)的相關(guān)資源, 以便釋放內(nèi)存地址.
顯然, 這種顯式的由編程人員自己控制釋放內(nèi)存的方式很容易出問(wèn)題, 忘了, 漏了, 都可能導(dǎo)致內(nèi)存問(wèn)題. 也不符合程序員要懶的特征.
故而, Java語(yǔ)言中引入了自動(dòng)內(nèi)存管理的機(jī)制, 也就是垃圾回收器. 大部分的現(xiàn)代面向?qū)ο笳Z(yǔ)言, 也都是采用自動(dòng)內(nèi)存管理機(jī)制.
內(nèi)存自動(dòng)管理回收機(jī)制可以解決大部分, 但不是所有的內(nèi)存問(wèn)題, 這也是為什么我們要討論內(nèi)存泄露.
垃圾回收器的職責(zé)
垃圾回收器有三大職責(zé):
- 分配內(nèi)存;
- 確保任何被引用的對(duì)象保留在內(nèi)存中;
- 回收不能通過(guò)引用關(guān)系找到的對(duì)象的內(nèi)存.
垃圾回收的一般流程
1.2 相關(guān)概念
垃圾回收(GC)
垃圾回收器中有一個(gè)進(jìn)程來(lái)做上面的這些事情, 這個(gè)進(jìn)程查找我們的對(duì)象引用的關(guān)系并釋放其內(nèi)存, 這個(gè)進(jìn)程就是garbage collection(垃圾回收), 也就是我們常說(shuō)的GC.
Heap和Stack
簡(jiǎn)單說(shuō)下:
- Heap內(nèi)存是指java運(yùn)行環(huán)境用來(lái)分配給對(duì)象和JRE類(lèi)的內(nèi)存. 是應(yīng)用的內(nèi)存空間.
- Stack內(nèi)存是相對(duì)于線程Thread而言的, 它保存線程中方法中短期存在的變量值和對(duì)Heap中對(duì)象的引用等.
- Stack內(nèi)存, 顧名思義, 是類(lèi)Stack方式, 總是后進(jìn)先出(LIFO)的.
- 我們通常說(shuō)的GC的針對(duì)Heap內(nèi)存的. 因?yàn)镾tack內(nèi)存相當(dāng)于是隨用隨銷(xiāo)的.
GC Root
直譯GC根, 我們姑且不譯了吧.
所謂GC Root我們可以理解為是一個(gè)Heap內(nèi)存之外的對(duì)象, 通常包括但不僅限于如下幾種:
- System Class 系統(tǒng)Class Loader加載的類(lèi). 例如java運(yùn)行環(huán)境中rt.jar中類(lèi), 比如java.util.* package中的類(lèi).
- Thread 運(yùn)行中的線程
- JNI 中的本地/全局變量, 用戶自定義的JNI代碼或是JVM內(nèi)部的.
- Busy Monitor 任何調(diào)用了wait()或notify()方法, 或是同步化的(synchronized)的東西. 可以理解為同步監(jiān)控器.
- Java本地實(shí)例, 還在運(yùn)行的Thread的stack中的方法創(chuàng)建的對(duì)象.
活對(duì)象/垃圾
如果這個(gè)對(duì)象是引用可達(dá)的, 則稱之為活的(live), 反之, 如果這個(gè)對(duì)象引用不可達(dá), 則稱之為死的(dead), 也可以稱之為垃圾(garbage).
這個(gè)引用可達(dá)與不可達(dá)就是相對(duì)于GC Root來(lái)說(shuō)的:
2, Java的內(nèi)存管理機(jī)制
2.1 關(guān)于JVM
我們平常在查看我們的java版本時(shí), 你會(huì)發(fā)現(xiàn):
$ java -version
java version "1.8.0_74"
Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)
其中有個(gè)HotSpot VM的東西, 那么這個(gè)是什么呢? 和JVM有什么關(guān)系呢?
在此簡(jiǎn)單說(shuō)下, 以便行文:
- JVM, Java虛擬機(jī), 可以簡(jiǎn)單理解為一種技術(shù)思想, 虛擬技術(shù)理念.
- HotSpot VM是JVM的一種實(shí)現(xiàn), 包含了服務(wù)器版和桌面應(yīng)用程序版, 現(xiàn)時(shí)由Oracle維護(hù)并發(fā)布.
我們當(dāng)前使用的sun(oracle)的java版本(應(yīng)該是1.3以上)都是內(nèi)置的HotSpot VM實(shí)現(xiàn). 所以接下來(lái)的分析也都是基于HotSpot VM的, 但是還是簡(jiǎn)稱JVM.
2.2 JVM內(nèi)存區(qū)域
JVM使用分代式的內(nèi)存管理方式, 將Heap分成三代 --- 新生代, 老一代, 持久代.
-
Young Generation
- 新生代.
- 所有new的對(duì)象.
- 該區(qū)域的內(nèi)存管理使用minor garbage collection(小GC).
- 更進(jìn)一步分成Eden space, Survivor 0 和 Survivor 1 三個(gè)部分.
-
Old Generation
- 老年區(qū).
- 新生代中執(zhí)行小粒度的GC幸存下來(lái)的"老"對(duì)象.
- 該區(qū)域的內(nèi)存管理使用major garbage collection(大GC).
-
Permanent Generation
- 持久代.
- 包含應(yīng)用的類(lèi)/方法信息, 以及JRE庫(kù)的類(lèi)和方法信息.
小GC執(zhí)行非常頻繁, 而且速度特別快.
大GC一般會(huì)比小GC慢十倍以上.
大小GC都會(huì)發(fā)出"Stop the World"事件, 也就是說(shuō)中斷程序運(yùn)行, 直至GC完成. 這也是我們?cè)?a href="http://www.reibang.com/p/1fb065c806e6" target="_blank">App優(yōu)化之消除卡頓中為什么說(shuō)頻繁GC會(huì)造成用戶感知卡頓.
3, GC的流程
了解了內(nèi)存Heap的幾個(gè)區(qū)域, 我們?cè)賮?lái)看下垃圾收集器是怎么利用這幾個(gè)區(qū)域來(lái)管理內(nèi)存和回收垃圾的.
1. 創(chuàng)建新的對(duì)象
每當(dāng)我們使用new創(chuàng)建一個(gè)對(duì)象時(shí), 這個(gè)對(duì)象會(huì)被分配到新生代的Eden區(qū)域:
2. 當(dāng)Eden區(qū)域滿時(shí)
當(dāng)Eden區(qū)域內(nèi)存被分配完時(shí), 小GC程序被觸發(fā):
引用可達(dá)的對(duì)象會(huì)移到Survivor(幸存者)區(qū)域--S0, 然后清空Eden區(qū)域, 此時(shí)引用不可達(dá)的對(duì)象會(huì)直接刪除, 內(nèi)存回收, 如下:
3. Eden再次滿時(shí)
當(dāng)Eden區(qū)域再次分配完后, 小GC執(zhí)行, 引用可達(dá)的對(duì)象會(huì)移到Survivor(幸存者)區(qū)域, 而引用不可達(dá)的對(duì)象會(huì)跟隨Eden的清空而刪除回收.
需要注意的是, 這次引用可達(dá)的對(duì)象移動(dòng)到的是S1的幸存者區(qū).
而且, S0區(qū)域也會(huì)執(zhí)行小GC, 將其中還引用可達(dá)的對(duì)象移動(dòng)到S1區(qū), 且年齡+1. 然后清空S0, 回收其中引用不可達(dá)的對(duì)象.
此時(shí), 所有引用可達(dá)的對(duì)象都在S1區(qū), 且S1區(qū)的對(duì)象存在不同的年齡. 如下:
當(dāng)Eden第三次滿時(shí), S0和S1的角色互換了:
依此循環(huán).
4. 當(dāng)Survivor區(qū)的對(duì)象年齡達(dá)到"老年線"時(shí)
上面1~3循環(huán), Survivor區(qū)的對(duì)象年齡也會(huì)持續(xù)增長(zhǎng), 當(dāng)其中某些對(duì)象年齡達(dá)到"老年線", 例如8歲時(shí), 它們會(huì)"晉升"到老年區(qū).
如此1~4步重復(fù), 大體流程是這樣的
參考
- http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
- http://www.ibm.com/support/knowledgecenter/SS3KLZ/com.ibm.java.diagnostics.memory.analyzer.doc/gcroots.html
- http://www.infoq.com/cn/articles/jvm-memory-collection
- http://www.journaldev.com/4098/java-heap-space-vs-stack-memory
轉(zhuǎn)載請(qǐng)注明出處, 歡迎大家分享到朋友圈, 微博~