天天看點

《深入了解Java虛拟機》個人讀書總結——JAVA虛拟機記憶體《深入了解Java虛拟機》個人讀書總結——JAVA虛拟機記憶體

《深入了解Java虛拟機》個人讀書總結——JAVA虛拟機記憶體

最近在讀《深入了解Java虛拟機》,網上對Java虛拟機的總結有很多,自己覺得自己也應該記錄一點個人的讀書總結,以便日後複習友善。

随着開發工作的逐漸深入,對Java的了解不能止步于crud,Java不像C語言,Javaer是不需要自己控制記憶體的,一旦出現常見的OutOfMemoryError或StackOverflowError,如果不了解虛拟機是怎樣使用記憶體的,那麼排查錯誤将會出現一定的阻礙。

運作時資料區域

Java虛拟機在執行Java程式的過程中會把它所管理的記憶體劃分為若幹個不同的資料區域。具體如圖所示:

《深入了解Java虛拟機》個人讀書總結——JAVA虛拟機記憶體《深入了解Java虛拟機》個人讀書總結——JAVA虛拟機記憶體

由圖可以看出,JVM将記憶體主要劃分為:方法區、虛拟機棧、本地方法棧、堆、程式計數器五大塊。

程式計數器

程式計數器是一塊較小的記憶體空間,在大學作業系統一課中我們知道程式計數器是用于存放下一條指令所在單元的位址的地方。為了保證程式(在作業系統中了解為程序)能夠連續地執行下去,處理器必須具有某些手段來确定下一條指令的位址。而程式計數器正是起到這種作用,是以通常又稱為指令計數器。在虛拟機的概念模型中,位元組碼解釋器工作時也是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令。

Java虛拟機的多線程是通過線程輪流切換并配置設定處理器執行時間片來實作,在任何一個确定的時刻,一個處理器都隻會執行一條線程腫的指令,是以,為了確定線程切換之後能恢複到正确的執行位置,每條線程都需要一個獨立的程式計數器,這樣就互不影響獨立存儲,是以程式計數器是線程私有的記憶體。

此記憶體區域是唯一一個在Java虛拟機規範中沒有規定任何OutOfMemoryError情況的區域。

虛拟機棧

虛拟機棧也是線程私有的,它的生命周期與線程相同。那個棧可以了解成我們平時所熟悉的資料結構的那個棧,裡面的一個個棧元素我們叫它棧幀(其實是一種資料結構,後面會說)。每個方法在執行的同時會建立一個棧幀,用于存儲局部變量表、操作數棧,動态連結、方法出口等資訊。然後每個方法從調用到執行結束就相當于這些棧幀在虛拟機棧中出棧入棧的過程。

在Java虛拟機規範中,對這個區域規定了兩種異常狀況:如果線程請求的棧深度大于虛拟機所允許的深度,将抛出StackOverflowError異常;如果虛拟機棧可以動态擴充(通過虛拟機參數-Xss控制),如果擴充到無法申請到足夠的記憶體就會抛出OutOfMemoryError異常。

本地方法棧

這個棧和上面所說的棧其實是作用相似的,虛拟機棧是為Java方法服務,這個本地方法棧則是為native方法服務。虛拟機規範抛出的異常也是和虛拟機規範一樣的,正因為虛拟機規範中沒怎麼硬性規定,sun的HotSpot虛拟機直接就将虛拟機棧和本地方法棧合二為一了。

堆可以說是虛拟機中所管理的記憶體中最大的一塊了。這塊記憶體是被所有線程都共享的,主要的作用是用來存放對象執行個體的。所有的對象執行個體和數組都要在堆上配置設定。當然,随着JIT編譯器的發展現在也不是那麼”絕對”了。

Java堆是垃圾收集器管理的主要區域,由于現在的收集器基本上采用的都是分代收集算法,所有Java堆可以細分為:新生代和老年代。在細緻分就是把新生代分為:Eden空間、From Survivor空間、To Survivor空間。在HotSpot中還提出了一個永久代的概念,從記憶體中摳出一部分用于存儲類的元資訊,類變量等内容,将其當成方法區來用,不過在Java8之後,這個概念被去掉了,換湯不換藥,現在改叫元空間。

Java虛拟機規範規定,堆可以處于實體上不連續的記憶體空間中,隻要邏輯上是連續的即可。在實作上即可以是固定大小的,也可以是可動态擴充的(通過虛拟機參數 -Xmx和-Xms控制)。如果在堆中沒有記憶體完成執行個體配置設定,并且堆大小也無法在擴充時,将會抛出OutOfMemoryError異常。

方法區

方法區和堆一樣,同樣是線程共享的記憶體區域,它用于存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料,可以通過虛拟機的參數-XXpermSize和-XX:MaxPermSize來限制方法區大小。

運作時常量池

常量池用于存放編譯期間生成的各種字面量和符号引用。這是一個class檔案中的一個區域。

Java程式并不一定要求常量在編譯期間産生,有可能在運作的時候會産生一個新的常量。為了實作這種動态性,在方法區中還有一個區域叫運作時常量池。在運作期間這裡的内容是可以修改的。同時,在類的加載後會把常量池的東西存放到運作時常量池。

這裡還要說的一個是字元串常量池,我想說的是這個和運作時常量池并沒有什麼關系。字元串常量池是線程共享的,在Java7之前是存在于方法區裡的,Java7之後被轉移到堆上了。現在要我說這兩個常量池是平級。

運作時方法區和方法區一樣,記憶體配置設定不足時會抛出OutOfMemoryError異常。

直接記憶體

直接記憶體不是虛拟機運作是資料區的一部分。也不是Java虛拟機規範中定義的記憶體區域。但是這部分記憶體也被頻繁地使用,也有可能會導緻OutOfMemoryError異常。

在JDK1.4中新加入了NIO,它可以使用Native函數庫直接配置設定堆外記憶體,這記憶體是不受虛拟機控制的,但會受到本機的限制。是以在設定虛拟機的參數-Xmx等參數時要考慮到各記憶體區域總和要小于實體記憶體限制才行,進而避免動态擴充的時候出現OutOfMemoryError異常。