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方法服務。 虛擬機規範中,對這個區域無強制規定,由具體的虛擬機自由實現 與虛擬機棧一樣, 本地方法棧區域也會拋出StackOverflowErrorOutOfMemoryError異常。
  • 虛擬機棧是為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的時間更

參考自