1. 程式人生 > >Java虛擬機器 記憶體管理與垃圾回收

Java虛擬機器 記憶體管理與垃圾回收

java和C++之間有一堵由記憶體自動分配與垃圾回收所圍成的高牆,外面的人想進來,裡面的人想出去

主要內容

記憶體分佈

垃圾回收機制

垃圾收集器

Java記憶體分佈

當java虛擬機器執行程式時,會把由虛擬機器管理的記憶體劃分為不同的區域,他們的作用不同,建立和銷燬時間也不同,有的是虛擬機器完全控制,有的是依賴使用者的啟動和結束而建立和銷燬。


僅僅是組成圖,並不代表實際佔用大小。比如程式計數器只佔很小空間

執行緒隔離就是不同執行緒之間都是獨立的不可以共享(虛擬機器棧,本地方法棧,程式計數器)

程式計數器:當前執行緒執行的位元組碼(編譯後的程式碼)的行號指示器。java虛擬機器的多執行緒是通過處理器快速切換並執行不同執行緒程式碼來實現的,程式計數器就相當於你同時看多本書時候的書籤。此外,當執行native方法(非java程式碼介面)時候計數器為空(undefined)。

java虛擬機器棧:執行緒私有且與執行緒生命週期相同。虛擬機器棧描述的是java方法執行的記憶體模型:每個方法在執行時都會建立一個棧幀(棧中的一個元素),用於儲存方法的一些資訊。每一個方法執行過程就相當於入棧出棧。當棧超出虛擬機器允許範圍,以及棧擴充套件時記憶體不足都會丟擲異常。

本地方法棧:與虛擬機器棧類似,只不過虛擬機器棧作用於執行java方法,而本地方法棧作用於native方法。

java堆:堆是虛擬機器管理的記憶體中最大的一塊,所有執行緒共享並虛擬機器啟動時就建立,唯一的目的就是用於存放物件例項。java堆是垃圾回收的主要物件。

方法區:執行緒共享,用於儲存已經被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。

執行時常量池:是方法區的一部分。class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有常量池,用於存放編譯器存放的各種字面量和符號引用,這部分內容將在類載入後存放到方法區的執行時常量池。執行時常量池不同於class檔案常量池,可以在執行期間更改比如String類的intern()方法。

直接記憶體:即不歸虛擬機器管理的記憶體。在有些場合能顯著提高效能,避免了在java堆和native堆中來回複製資料,比如nio中通道與緩衝區的應用。

垃圾回收

垃圾回收主要處理的是堆記憶體,堆不像方法區一樣記憶體的分配和回收都具有確定性,一個介面中多個實現類需要的記憶體可能不一樣,一個方法中多個分支需要的記憶體也可能不一樣,只有在程式執行時候才知道會建立哪些物件,這部分記憶體的分配和回收具有動態性,即垃圾回收主要關注的部分。

確定回收物件

引用計數法:給物件中新增一個計數器,每當物件被引用時計數+1,引用失效時計數-1,且當計數器為0時就是不再被使用的。但是Java中沒有用這種辦法來管理記憶體,原因是很難解決物件互相引用的問題。

引用:為了達到更好的效果引用不是一個非黑即白的概念,java1.2後將引用分為強引用、軟引用、弱引用、虛引用,強度逐一減弱。即當記憶體不足時優先處理弱的引用。

回收方法區:方法區(永久代)的垃圾收集主要回收兩類:廢棄常量和無用的類。

垃圾收集演算法

標記-清除:標記處所有需要回收的物件,標記完成後統一回收被標記的物件。缺點是標記和清除時效率都不高,而且清理完成後會產生大量不連續的記憶體空間。

複製演算法:為了提高效率而出現,將記憶體按容量分為大小相等的兩塊,每次只使用一塊,當一塊的記憶體滿了的時候把還存活的物件複製到另一個塊中,並清除之前塊的記憶體。現在大部分虛擬機器都採用這種方法收回新生代記憶體,由於新生代物件98%都是朝生夕死的,所以把記憶體分為一塊較大的Eden空間和兩塊Survivor空間(8:1),每次使用Eden和其中一塊Survivor空間,回收時把存活的物件複製到另一個Survivor空間中並回收掉剛才用的空間。當Survivor空間不夠用時需要依賴其他記憶體(老年代)進行擔保分配,即直接進入老年代。

標記-整理:與標記-清除類似,但不是直接對可回收物件進行清理,而是將將所有存活物件移到一端,清除這端邊界之外的。

分代收集演算法:堆記憶體分為新生代和老年代;新生代複製演算法,老年代使用標記-清除/整理演算法。

《深入理解Java虛擬機器:JVM高階特性與最佳實踐》複習筆記