都說搞C的牛叉,那是因為C解決問題,全靠程式設計師自己,他們對自己的程式在記憶體中是什麼樣瞭如指掌。而Java呢不需要有太多作業系統的知識,不用時時注意記憶體的問題,但這不代表我們就不用去了解它背後的原理,(其實我是被公司的C嘲諷了一下OoO)。 Java之所以容易上手,那是因為最困難的問題,已經被前人解決了,而這一切都歸功於Java Virtual Machine-Java虛擬機,JVM其實就是一台抽象的計算機,它有自己的指令集,有自己的機器語言,有自己的記憶體管理。本系列會一一解開它的真面目。
本文基於Java HotSpot™ 虛擬機,JDK 1.8,將討論:
JVM 內部結構
JVM 記憶體管理
JVM 記憶體模型
JVM 記憶體管理
JVM 記憶體模型一個程式執行的過程是這樣的,以C語言為例,原始碼首先被編譯成可執行文件,以二進制的形式存放到磁碟上,當執行時,首先從磁碟載入到記憶體中,然後處理器就開始執行目標程式中機器指令。反觀Java,先編譯成字節碼文件,與平台無關,JVM透過ClassLoader載入到記憶體中,然後執行其中的機器指令,JVM幫我們跟作業系統打交道。有了字節碼和JVM,Java實現了平台無關性。 JVM也可以認為是一個進程,在啟動時申請一塊內存,然後按照功能的不同,把內存分為以下不同區域:(1) Heap 堆,一個非常重要的區域,被所有線程共享,基本所有的物件實例都在這裡分配,大部分的垃圾回收也都發生在這裡。這部分內存,由JVM使用Garbage Collector(自動內存管理工具)來管理,職責就是為物件分配內存,釋放空閒內存。 Java 堆的大小可以使用參數來控制是固定的,還是動態擴充的。 (2) JVM Stacks 棧,與執行緒息息相關,執行緒私有,隨執行緒生而生,死而死,是執行緒執行工作的記憶體。 HotSpot中Java棧和本地方法棧合而為一,都在本地記憶體空間中分配,這部分記憶體就不需要JVM刻意的去管理了。 JVM棧主要用來儲存棧幀,當呼叫一個方法時建立一個棧幀,方法結束銷毀,從棧的角度來看,就是入棧和出棧兩個操作。 棧幀是一個資料結構,用來儲存局部變量,操作數棧,和當前類別運行時常數池的參考。局部變數數組:用來保存方法內定義的基本型別變量,下標從0 開始,JVM使用局部變數表傳遞方法參數,當呼叫一個實例方法,第0 位置儲存的是目前物件的this 參考;運算元堆疊:用來執行運算和準備呼叫方法的參數以及方法的回傳結果;動態連結:引用物件的運行時常數池。 (3) PC Register 程式計數器,線程私有,主要作用就是儲存指令位址,取指,解碼和執行。每個執行緒都關聯著唯一的堆疊和PC暫存器。 (4) Metaspace 元空間,在JDK8之前的HotSpot VM稱之為方法區或永久代,被各線程共享,它存儲了一個類的結構信息,如常量池、字段、方法等。存放在本地記憶體中,與堆不相關。 (5) Native Method Stacks JVM棧是為Java方法準備的,那麼本地方法棧則是為虛擬機器呼叫本地方法服務的。 2. JVM 記憶體管理 Java不允許直接操作內存,記憶體的申請和釋放統一交給虛擬機處理。 2.1 Garbage Collection 自動記憶體管理 自動記憶體管理(以下簡稱GC)的職責:分配記憶體確保引用對象保留在記憶體回收不可達引用物件的記憶體的大部分記憶體記憶體分配問題,它本身也佔用一定的資源。當堆滿了或它的某個組成部分達到一個閥值就會觸發垃圾回收。垃圾回收主要從這幾方面考慮:回收的頻率和時間,例如堆小那麼回收的次數就多,堆大回收次數少,但回收一次的時間長;記憶體碎片問題;多執行緒程式下的垃圾回收。 垃圾回收策略:(1)串列與並行(Serial versus Parallel)
串行,同一時間只能有一個垃圾回收線程工作;並行,在多CPU系統中,可有多個垃圾回收線程同時工作。
(2)併發與Stop-the-world(Concurrent versus Stop-the-world)
Stop-the-world 垃圾回收器,在回收期間,應用程式完全暫停工作,此時堆就相當於被凍結了,物件的狀態不可變;並發,一個或多個垃圾收集任務與應用程式同時執行,可能會出現短暫的Stop-the-world,在收集時,物件的狀態可能會改變。
(3)複製(Copying)
將內存分為兩半,回收時將存活的對象複製到另一半空間,然後清除當前內存,後續內存的分配比較容易,但內存的利用率比較低。
(4) 標記清除與標記整理(Compacting versus Non-compacting)
標記清除,標記可回收的對象,統一進行回收,不進行內存壓縮,會產生大量的內存碎片,在分配大對象時,可能無法找到連續的記憶體;標記整理,在標記完後,先對記憶體進行壓縮整理,把所有存活的物件放到一起回收。
(5)分代收集
把堆分成幾個區域,新生代和老年代,不同的區域使用上面的不同的回收方法。
2.2 HotSpot 中的分代收集
在HotSpot 中,把內存分為新生代和老年代,新生代又分為Eden和兩個大小一樣的Survivor空間,大部分對像在Eden分配,一些大對象可能直接分配到老年代。
堆的結構如下:
圖 2 堆