【Android 内存优化】Java 内存模型 ( Java 虚拟机内存模型 | 线程私有区 | 共享数据区 | 内存回收算法 | 引用计数 | 可达性分析 )
文章目錄
- 一、 Java 虛擬機內存模型
- 二、 程序計數器 ( 線程私有區 )
- 三、 虛擬機棧 ( 線程私有區 )
- 四、 本地方法棧 ( 線程私有區 )
- 五、 方法區 ( 共享數據區 )
- 1. 方法區
- 2. 運行時常量池
- 六、 堆區 ( 共享數據區 )
- 七、 內存溢出類型
- 八、 引用計數算法回收內存
- 九、 可達性分析算法回收內存
一、 Java 虛擬機內存模型
Java 內存優化 , 首當其沖就是處理 Java 內存泄漏問題 , 這是 Java 程序最主要的內存問題 , 大量的內存泄漏會導致內存溢出 ;
Java 虛擬機內存機制 : Java 虛擬機中內存分為兩部分 , 線程私有部分 , 共享數據區 ;
① 共享數據區 : 方法區 ( Method Area ) , 堆區 ( Heap Area ) ; 其中方法區中包含常量池 ;
② 線程私有數據區 : 程序計數器 ( PC ) , 虛擬機棧 ( VM Stack ) , 本地方法棧 ( Native Method Stack ) ;
這是 Java 虛擬機規范定義的內存分區 , 但是具體的廠家實現可能不完全一致 , 如 Sun JDK , Open JDK 等 ;
Android 中的 Java 虛擬機 跟上述 Java 規范有很大不同 ;
二、 程序計數器 ( 線程私有區 )
程序計數器 :
① 作用 : 該內存空間很小 , 主要用于指示執行的代碼行 , 程序計數器指向的代碼行 , 就是下一行將要執行的代碼 ;
② 線程切換運行 : Java 多線程是搶占式執行的 , 經常出現線程 AAA 執行時 , 切換到線程 BBB , 如果線程 BBB 執行完畢回到線程 AAA , 這里就需要記住線程 AAA 之前執行到哪了 , 這就需要用到線程私有的數據區的程序計數器 ( PC ) ;
③ 執行 Java 代碼 : 線程執行 Java 代碼時 , 程序計數器記錄的是虛擬機字節碼地址 ;
④ 執行 Native C/C++ 代碼 : 線程執行 native 代碼時 , 程序計數器記錄的 值是空值 null ;
程序計數器 區域沒有定義 內存溢出 異常 , 這個區域很小 ;
三、 虛擬機棧 ( 線程私有區 )
1. 虛擬機棧 ( VM Stack ) : 其生命周期與線程相同 , 描述的是 Java 方法執行的內存模型 , 該區域就是棧區 , 與堆區相對應 ;
2. 虛擬機棧中保存的數據 :
- 局部變量表
- 操作棧
- 方法返回地址
- 動態鏈接
- 額外附加信息
四、 本地方法棧 ( 線程私有區 )
本地方法棧 ( Native Method Stack ) : 這是 Native 層 C/C++ 提供的棧內存空間 , 該內存的類型與虛擬機棧內存類型一樣 , 只是語言不同 , 一個 Java 方法的額棧 , 一個是 C/C++ 方法的棧 ;
Hotspot VM 虛擬機中 , 虛擬機棧 與 本地方法棧是一塊內存 , 二者合二為一 ;
五、 方法區 ( 共享數據區 )
1. 方法區
方法區 : 存儲以下內容 ;
- 類信息 , 如 ClassLoader 加載的 Class
- 常量 , 存放在運行時常量池中 , 該常量池也是方法區的一部分 ;
- 靜態變量 , static 變量
- 即時編譯器( JIT compiler ) 編譯后的代碼
不同的虛擬機 , 實現不同 ;
該區域一般不進行 GC 垃圾回收 ;
2. 運行時常量池
運行時常量池 :
- 編譯中的 Java 常量 ( public static final )
- 字符串常量 ( String )
- final 修飾的常量 ;
- 符號引用 , 如 類或接口完整名稱 ( 帶包名 ) , 字段名 , 方法名 , 描述符 ;
六、 堆區 ( 共享數據區 )
Java 堆區 :
① 最大區域 : 該內存區是 Java 虛擬機管理的內存中最大的部分 , 是垃圾回收算法 GC 的主要操作區域 ;
② 內存溢出 : OOM ( OutOfMemory ) 內存溢出就是該區域內存被全部占用 , 無法為新的內存申請更多空間 ;
七、 內存溢出類型
內存溢出 :
① 棧內存溢出 : 在 Java 的棧區內存溢出 , 就是 StackOverflowException 棧溢出異常 , 在遞歸的時候 , 如果沒有控制好 , 就會報該異常 ;
② 堆內存溢出 : 在 Java 堆內存中的溢出 , 就是 OutOfMemoryError 堆內存溢出 , 在加載大量數據到內存時 , 會出現該異常 ;
八、 引用計數算法回收內存
引用計數是早期的 GC 回收 Java 對象機制 , 有一定弊端 ;
1. 引用計數簡介 : 使用對象的引用計數 , 確定 Java 對象是否存活 , 確定是否應該被回收 ;
2. 引用計數垃圾回收算法示例說明 :
① 創建對象 : 創建一個 OOO 類型對象 ooo , 此時引用計數為 0 , 如果不將其賦值給一個變量 , 那么很快就會被回收 ;
② 變量 AAA 賦值 : 創建一個 OOO 類型對象 ooo , 將對象 ooo 其 賦值 給變量 AAA , 此時該對象 AAA 引用計數為 111 ;
③ 變量 BBB 賦值 : 創建一個 OOO 類型對象 ooo , 將對象 ooo 其 賦值 給變量 BBB , 此時該對象 BBB 引用計數為 111 ;
④ BBB 引用 AAA : 變量 BBB 中有 OOO 類型成員變量 , 將 AAA 賦值 給該成員變量 , 此時對象 BBB 引用計數變成 222 ;
⑤ AAA 引用 BBB : 變量 AAA 中有 OOO 類型成員變量 , 將 BBB 賦值 給該成員變量 , 此時對象 AAA 引用計數變成 222 ;
此時即使把 A,BA , BA,B 兩個變量都設置成 null , 每個變量的引用計數都減一 , 也無法將引用計數減為 000 , 該對象永遠無法回收 ;
引用計數弊端 : 如果兩個變量之間互相引用 , 引用計數永遠不能變為 000 ;
九、 可達性分析算法回收內存
1. 可達性分析算法 : 以 GC Root 為分析的起點 , 查找對象的引用 , 如果找到一個對象 , 無法被 GC Root 直接或間接引用到 , 那么該對象就可以被回收了 ;
2. GC Root 對象 : GC Root 是一個對象 , 可以是如下對象 ;
- 虛擬機棧正在運行的引用
- 靜態屬性
- 常量
- JNI 中的對象
GC Root 就是不會被回收的那些的變量 , Android 中就是 Application , 單例類 , 運行中的 Activity 等 ;
3. 第一次掃描回調 finalize 方法 : 對象經過可達性分析后 , 發現沒有引用鏈可以達到 GC Root , 此時就會調用該對象的 finalize() 方法進行標記 , 開發者可以實現該方法 , 進行一些邏輯處理 :
- ① 釋放資源 : 可以執行一些資源釋放方法 , 一面出現內存泄漏 ;
- ② 引用自救 : 將對象賦值給指定變量 , 這樣可以避免被 GC 回收內存 ;
4. 可達性分析中對對象的兩次掃描 : 可達性分析時 , 需要對指定對象標記兩次 , 第一次被標記時會調用該對象 finalize() 方法 , 相當于判了死緩 , 此時可以通過添加引用的方式自救 , 如果沒有進行任何干預 , 第 222 次掃描到該對象還沒有到 GCRoot 的引用鏈 , 此時不會調用 finalize() 方法 , 直接就被回收了 ;
總結
以上是生活随笔為你收集整理的【Android 内存优化】Java 内存模型 ( Java 虚拟机内存模型 | 线程私有区 | 共享数据区 | 内存回收算法 | 引用计数 | 可达性分析 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 性能优化】布局渲染优化
- 下一篇: 【Android 内存优化】Java 引