Android | App記憶體優化 之 JVM & Android記憶體管理機制
- 2019 年 11 月 27 日
- 筆記
Java記憶體分配

圖自慕課網
方法區:
- 又叫
靜態區
,跟堆一樣,被所有的執行緒共享。 方法區包含所有的class文件
和static變數/方法
!!! - 方法區中包含的都是在
整個程式中
永遠唯一的元素
,如class,static變數。 - 用於存儲 已被虛擬機載入的
類資訊、常量、靜態變數
、即時編譯器編譯後
的程式碼/Java Class文件
等數據。 - 與Java堆一樣,是各個執行緒
共享
的記憶體區域。!!!! - 人們更願意把這個區域稱為「
永久代
」(Permanent Generation), 在發布的JDK1.7的HotSpot中,已經把原本放在永久代的字元串常量池移出。 它還有個別名叫做Non-Heap(非堆
)。 - 除了和
Java堆
一樣, 不需要連續的記憶體
和 可以選擇固定大小
或可擴展外
, 還可選擇不實現GC
。 - 在
Java虛擬機規範
中, 當方法區
無法滿足記憶體分配需求
時,將拋出OutOfMemoryError
異常。
棧
- 每個執行緒包含一個
棧區
, 棧中只保存基礎數據類型
的值
和對象
以及基礎數據
的引用
(Java語言提供了八種基本數據類型
: 六種數字類型(四個整數型long、int、short、byte,兩個浮點型float、double), 一種字元類
型String,還有一種布爾型
) - 每個棧中的數據(
基礎數據類型
和對象引用
)都是私有
的, 其他棧不能訪問。 - 棧分為3個部分:
基本類型變數區
、執行環境上下文
、操作指令區
(存放操作指令)。
虛擬機棧
- 每個方法在執行的同時都會創建一個
棧幀
, 用於存儲局部變數表
、操作數棧
、動態鏈接
、方法出口
等資訊。 - 每一個
方法
從調用
直至執行完成
的過程, 就對應著一個棧幀
在虛擬機棧中入棧
到出棧
的過程。
局部變數表
存放了編譯期可知的 各種基本數據類型
、對象引用類型
和returnAddress類型
, 它所需的記憶體空間
在編譯期間完成分配
。
- 是
執行緒私有的記憶體
,與執行緒生命周期
相同。!!!! - 一般把Java記憶體區分為
堆記憶體(Heap)
和棧記憶體(Stack)
, 其中『棧』指的是虛擬機棧
,『堆』指的是Java堆
。 - 在
Java虛擬機規範
中,對這個區域規定了兩種異常狀況:- 如果
執行緒請求
的棧深度
大於虛擬機所允許的深度
, 將拋出StackOverflowError
異常; - 如果虛擬機棧
可動態擴展
且擴展時
無法申請到足夠的記憶體
, 將拋出OutOfMemoryError
異常。
- 如果
本地方法棧
- 存儲局部變數表、操作數棧等;
- 是虛擬機使用到的
Native方法
服務。 在虛擬機規範
中,對這個區域無強制規定
,由具體的虛擬機自由實現
。 與虛擬機棧一樣, 本地方法棧區域也會拋出StackOverflowError
和OutOfMemoryError
異常。 虛擬機棧
是為Java方法服務
的;本地方法棧
是為Native方法服務
的;- 當然還要注意String的特殊性
- 一個例子:
- 還有一例:
堆
- 存儲的全部是對象, 每個對象都包含一個與之對應的class的資訊。 (class的目的是得到操作指令)
- jvm只有一個堆區(heap)被所有執行緒共享,堆中不存放基本類型和對象引用,只存放
對象本身
- 被所有執行緒共享的一塊記憶體區域,在虛擬機啟動時創建; 包含
一切new出來的對象
; - 每一個對象的
實際分配記憶體
都是在堆
上進行分配
的; 用於存放幾乎所有的對象實例和數組。
在Java堆中, 可能劃分出多個
執行緒私有
的分配緩衝區
(Thread Local Allocation Buffer,TLAB), 但無論哪個區域,存儲的都仍然是對象實例
, 進一步劃分的目的是 為了更好地回收記憶體
, 或者更快地分配記憶體
。
- 在
虛擬機棧
中,分配的只是引用
,虛擬機棧
當中的引用
,會指向在堆
中真正創建的對象
; - 是
GC主要作用、管理
的區域
,因為所佔記憶體最大,最有可能產生垃圾,也被稱做「GC堆」; 經常說的記憶體泄漏
也是發生在此區域; - 是Java虛擬機所管理的
記憶體
中最大的一塊
。 - 可處於
物理上不連續
的記憶體空間
中,只要邏輯上
是連續
的即可。 - 在
Java虛擬機規範
中, 如果在堆中沒有記憶體完成實例分配,且堆也無法再擴展時, 將會拋出OutOfMemoryError
異常。
程式計數器(Program Counter Register)
- 是
當前執行緒
所執行的位元組碼
的行號指示器
。- 如果
執行緒
正在執行的是一個Java方法
, 那麼計數器
記錄的是正在執行
的虛擬機位元組碼指令
的地址
; - 如果
執行緒
正在執行的是一個Native方法
, 那麼計數器的值
則為空
。
- 如果
**注意:!!!!!!!
計數器的值
代表著下一條
需要執行的位元組碼指令
,!!! 位元組碼解釋器
工作時, 就是通過改變這個計數器的值
來選取下一條
需要執行的位元組碼指令
,!!!! 分支、循環、跳轉、異常處理、執行緒恢復
等基礎功能
都需要依賴這個計數器
來完成。**
- 為了
執行緒切換
後能恢復
到正確的執行位置
,每條執行緒
都需要有一個獨立的程式計數器
, 各條執行緒之間計數器
互不影響,獨立存儲
, 因此它是執行緒私有
的記憶體。!!!!!!! - 在
Java虛擬機規範
中, 是唯一
一個沒有規定
任何OutOfMemoryError
情況的區域。
JVM垃圾回收演算法
-
回收演算法有以下四種
:-
分代收集演算法(1)
:是當前商業虛擬機都採用的一種演算法,根據對象存活周期的不同,將Java堆劃分為新生代和老年代,並根據各個年代的特點採用最適當的收集演算法。-
新生代
:大批對象死去,只有少量存活。使用『複製演算法』,只需複製少量存活對象即可。-
複製演算法(2)
:把可用記憶體按容量劃分為大小相等的兩塊
,每次只使用其中的一塊。 當這一塊的記憶體用盡後,把還存活
著的對象『複製』
到另外一塊上面, 再將這一塊記憶體空間一次清理
掉。
-
-
老年代
:對象存活率高。使用『標記—清理演算法』或者『標記—整理演算法』,只需標記較少的回收對象即可。-
標記-清除演算法(3)
:首先『標記』出所有需要回收的對象,然後統一『清除』所有被標記的 -
標記-整理演算法(4)
:首先『標記』出所有需要回收的對象,然後進行『整理』,使得存活的對象都向一端移動,最後直接清理掉端邊界以外的記憶體。
-
-
-
-
標記-清除演算法
效率其實不高, 它需要從頭到尾對記憶體中的每一個對象做標記; 並且會產生大量的不連續的記憶體碎片;

如上的第四行記憶體,可能兩塊藍色之間的那一塊記憶體都是用不了的, 只能用後面的三塊來分配, 即前面出現了記憶體空洞;
-
複製演算法
的相較於標記-清除演算法
,效率是高一點的, 每一次只需對二分之一的記憶體進行標記, 同時避免記憶體空洞; 但是浪費了一半空間,代價大;

-
標記-整理演算法
避免標記-清理導致的記憶體碎片(及記憶體空洞); 避免複製演算法的空間浪費;

Android記憶體管理機制
記憶體(按需)彈性分配
分配值
與最大值
受具體設備影響; 不同
配置的手機,其單個APP可以使用的記憶體是不同
的; 比如多者有單個APP可以使用512M的記憶體的,少者128M甚至更甚;
OOM場景:
OOM有時候是APP自己的原因,有時候也可能是整個系統的原因;
- APP使用記憶體真正不足,超限: 比如某一個手機,其單個APP 最大可以使用的記憶體 是512M, 假設有一個APP 已經使用了510M了,這時候如果還要再申請一個3M的空間, 這時候記憶體是真正不足了,超過了最大限制,要拋出OOM記憶體溢出異常;
- 系統可用記憶體不足: 就是, 即使 APP使用的記憶體 沒有超過 系統規定的最大限制, 但是
整個系統的記憶體
已經不夠用了,AMS回收了別的進程 也不夠分了, 沒辦法多分配給APP記憶體了, 這時候也會拋出OOM 記憶體溢出異常
; 如某一個手機,其單個APP 最大可以使用的記憶體 是512M, 一個APP只用了200M,再要申請一個幾十M的記憶體時, 系統也拋出OOM記憶體溢出異常
;
Dalvik 和 ART的區別(關注點:程式運行時、GC演算法)
參考鏈接:
Android 4.4之前,Android系統一直都是在Dalvik 虛擬機上的, 從Android 4.4開始開始引入ART,到5.0已經成為默認選擇。
-
Dalvik
僅固定一種回收演算法
,!!!! 手機出廠之前已經設定好了,運行期間無法改變; 另外, 應用程式每次運行時
,!!!!都需要
將程式內的程式碼即使轉變為機器碼才能運行,這無形中多附加了一道手續, 這就造成了耗電相對較快、佔用記憶體大、即使是旗艦機用久了也會卡頓嚴重的現象。
- ART,Android Runtime 的簡稱。
- 優點:
- 通過在安裝應用程式時,自動對程式進行程式碼預讀取編譯, 讓程式
直接編譯成機器語言,運行時直接運行 無需再做轉化
,!!!! 免去了Dalvik模式運行時要時時轉換程式碼, - 實現高效率、省電、佔用更低的系統記憶體、手機運行流暢。
- 通過在安裝應用程式時,自動對程式進行程式碼預讀取編譯, 讓程式
- 缺點:
- 佔用略高一些的存儲空間;
- 安裝程式時要相比普通 Dalvik 模式要長一些時間來實現預編譯;
- Android5.0之後都是默認使用
ART
虛擬機, 其回收演算法
,是可以在APP運行期間進行選擇
的,!!!! 可以在不同的情況下,選擇合適的垃圾回收演算法
; 如果, APP正跑在前台
,和用戶正在交互, 此時此景,自然響應速度
最重要! 對於用戶來說,需要APP能夠及時響應, 此時應該選擇一種簡單的演算法——標記-清除演算法
; 如果, APP切到了後台
, 則可以選擇標記-整理演算法
,作為補充; (也就是說,ART 相對於 Dalvik 而言, 具備記憶體整理能力
,減少記憶體空洞
)
Low Memory Killer 機制
機制目的:保證大多數情況下,不會出現記憶體不足的情境;
- 針對所有進程;
- 當手機記憶體不足,Low Memory Killer 機制就會 針對所有進程 進行回收;
進程分類
: Android系統將進程分為以下幾類: (進程優秀級從前往後,從高到低) 前台進程,可見進程,服務進程,後台進程,空進程; (Foreground進程、Visible進程、Service進程、Background進程、Empty進程)
如果用戶按Home鍵返回桌面,那麼該app成為Background進程; 如果按Back返回,則成為Empty進程。
- RAM(記憶體)不足時, Low Memory Killer 會找優先順序低的進程,優先進行回收, 殺死優先順序較低的進程,讓高優先順序進程獲取更多記憶體; 同時還會考慮一個因素——
回收收益
, 即 回收 某一個進程 能 收回 多大的記憶體; ActivityManagerService
直接管理所有進程的記憶體資源分配
。 所有進程要申請
或釋放記憶體
都需要通過ActivityManagerService
對象。- 垃圾回收不定期執行。 當記憶體不夠時就會遍歷heap空間,把垃圾對象刪除。
堆記憶體
越大
,則GC
的時間更長
。