還在學JVM?我都幫你總結好了(附腦圖)

本文腦圖

運行時數據區模型

在java虛擬機中把內存分爲若干個不同的數據區域。這些區域有各自的用途,有些區域隨着虛擬機進程啓動而存在,有些區域則依賴用戶線程的啓動和結束而建立和銷燬。在JVM中主要分爲以下幾個區域:

  1. 程序計數器
  2. 方法區
  3. 虛擬機棧
  4. 本地方法棧
  5. java堆

在這裏插入圖片描述

程序計數器

程序計數器是內存中較小的一部分區域,是當前線程執行的字節碼的行號指示器。在字節碼解釋器工作時通過計數器的值來選取下一條指令。

爲什麼需要計數器?
多線程情況下,一條線程中有多個指令,爲了使線程切換可以恢復到正確執行位置,每個線程都具有各自獨立的程序計數器,所以該區域是非線程共享的內存區域。

如果執行的是Java方法,計數器記錄的是正在執行的字節碼指令的地址;若執行的是Native方法,計數器存儲爲空。這塊內存區域是虛擬機規範中唯一沒有OutOfMemoryError的區域。

我們可以得出程序計數器主要有兩個作用:

  1. 字節碼解釋器通過改變程序計數器來依次讀取指令,從而實現代碼的流程控制,如:順序執行、選擇、循環、異常處理。
  2. 在多線程的情況下,程序計數器用於記錄當前線程執行的位置,從而當線程被切換回來的時候能夠知道該線程上次運行到哪兒了。

方法區

方法區也稱"永久代",它用於存儲虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據,是各個線程共享的內存區域。

在JDK8之前的HotSpot JVM,存放這些”永久的”的區域叫做“永久代(permanent generation)”。永久代是一片連續的堆空間,在JVM啓動之前通過在命令行設置參數-XX:MaxPermSize來設定永久代最大可分配的內存空間,默認大小是64M(64位JVM默認是85M)。

方法區或永生代相關設置

  • -XX:PermSize=64MB 最小尺寸,初始分配
  • -XX:MaxPermSize=256MB 最大允許分配尺寸,
  • 按需分配XX:+CMSClassUnloadingEnabled
  • -XX:+CMSPermGenSweepingEnabled 設置垃圾不回收
  • -server選項下默認MaxPermSize爲64m-client選項下默認MaxPermSize爲32m

java虛擬機規範堆方法去區限制非常的寬鬆,可以不選擇垃圾回收,以及不需要連續的內存和可擴展的大小。這個區域主要是針對於常量池的回收以及對類型的卸載,當方法區無法分配到足夠的內存的時候也會拋出OOM。

虛擬機棧

虛擬機棧是每個java方法的內存模型:每個方法被執行的時候都會創建一個"棧幀",用於存儲局部變量表(包括參數)、操作棧、方法出口等信息。每個方法被調用到執行完的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。

平時說的棧一般指局部變量表部分。棧幀對應的結構圖,如下圖所示:

局部變量表所需要的空間在編譯期完成分配,當執行一個方法時,該方法需要在棧幀中分配多大的局部變量表的空間完全是可以確定的,因此在方法運行的期間不會改變局部變量表的大小。

初級程序員可能籠統的將Java內存分爲堆內存棧內存時,該區域就是常說的棧內存,該區域的局部變量表存放基本類型、對象的引用類型,在對象的引用類型中存儲的是指向對象的地址

該區域會出現兩種異常

  1. 當線程請求的棧深度大於虛擬機所允許的深度,就會拋出StackOverflowError異常。
  2. 一般虛擬機的內存都是動態擴展的,但是有可能動態的擴展還是配不到足夠的內存,就會拋出OOM異常。

本地方法棧

本地方法棧(Native Method Stacks)與虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務。

本地方法棧爲虛擬機使用到的native方法服務,可能底層調用的c或者c++,我們打開jdk安裝目錄可以看到也有很多用c編寫的文件,可能就是native方法所調用的c代碼。

Java堆

Java 堆(Java Heap)是Java 虛擬機所管理的內存中最大的一塊。Java 堆是被所有線程共享的一塊內存區域,在虛擬機啓動時創建。

此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裏分配內存。堆內存是所有線程共有的,可以分爲兩個部分:年輕代老年代

注意:它是所有線程共享的,它的目的是存放對象實例。同時它也是GC所管理的主要區域,因此常被稱爲GC堆。根據虛擬機規範,Java堆可以存在物理上不連續的內存空間,就像磁盤空間只要邏輯是連續的即可。它的內存大小可以設爲固定大小,也可以擴展。當前主流的虛擬機如HotPot都能按擴展實現(通過設置 -Xmx-Xms),如果堆中沒有內存內存完成實例分配,而且堆無法擴展將報OOM錯誤(OutOfMemoryError)

新生代又分爲:Eden空間、From SurvivorTo Survivor空間。進一步劃分的目的是更好地回收內存,或者更快地分配內存。

分代回收的原因:對象的生命週期不同,所以針對不同生命週期的對象可以採取不同的回收方式,以便提高回收效率。從內存分配的角度來看,線程共享的java堆中可能會劃分出多個線程私有的分配緩衝區

垃圾回收算法

標記-清除

標記-清除是最基本的回收算法,後面的算法的設計的思想都是基於此算法進行設計。標記-清除算法一共分爲兩個階段標記清除階段,標記階段是將不可達的對象(即爲不存活的對象)進行標記,接着清除階段將這些標記的對象進行清除。

標記-清除算法主要有兩個問題:一個是效率的問題,標記和清除兩個過程效率都不高;另一個問題就是該算法會產生很多的內存碎片,大量的不連續內存空間,當程序需要申請較大的內存空間存儲大對象的時候,有可能無法申請到足夠的內存空間而不得不再一次觸發一次垃圾回收動作。

複製算法

複製算法將內存劃分爲兩個區間,在任意時間點,所有動態分配的對象都只能分配在其中一個區間(稱爲活動區間),而另外一個區間(稱爲空閒區間)則是空閒的。

當有效內存空間耗盡時,JVM將暫停程序運行,開啓複製算法GC線程。接下來GC線程會將活動區間內的存活對象,全部複製到空閒區間,且嚴格按照內存地址依次排列,與此同時,GC線程將更新存活對象的內存引用地址指向新的內存地址。

此時,空閒區間已經與活動區間交換,而垃圾對象現在已經全部留在了原來的活動區間,也就是現在的空閒區間。事實上,在活動區間轉換爲空間區間的同時,垃圾對象已經被一次性全部回收。

新生代中因爲對象都是"朝生夕死的",深入理解JVM虛擬機上說98%的對象存活率很低,適用於複製算法,複製算法比較適合用於存活率低的內存區域。它優化了標記/清除算法的效率和內存碎片問題。

JVM不是平分內存,新生代中由於存活率低,不需要複製保留那麼大的區域造成空間上的浪費,因此不需要按1:1劃分內存區域,而是將內存分爲一塊Eden空間和From Survivor、To Survivor【保留空間】。

(1)新生代:大多數對象在新生代中被創建,其中很多對象的生命週期很短。每次新生代的垃圾回收(又稱Minor GC)後只有少量對象存活,所以選用複製算法,只需要少量的複製成本就可以完成回收。

在新生代內存中每次只是用Eden和其中的一個S區。當Eden區滿時,還存活的對象將被複制到兩個Survivor區中的一個。當這個Survivor區滿時,此區的存活且不滿足“晉升”條件的對象將被複制到另外一個Survivor區。

對象每經歷一次Minor GC,年齡加1,達到“晉升年齡閾值”後,被放到老年代,這個過程也稱爲“晉升”。顯然,“晉升年齡閾值”的大小直接影響着對象在新生代中的停留時間,在SerialParNew GC兩種回收器中,“晉升年齡閾值”通過參數MaxTenuringThreshold設定,默認值爲15。

(2)老年代:在新生代中經歷了N次垃圾回收後仍然存活的對象,就會被放到年老代,該區域中對象存活率高。老年代的垃圾回收(又稱Major GC)通常使用標記-清理標記-整理算法。整堆包括新生代和老年代的垃圾回收稱爲Full GC

(3)永久代:主要存放元數據,例如ClassMethod的元信息,與垃圾回收要回收的Java對象關係不大。相對於新生代和年老代來說,該區域的劃分對垃圾回收影響比較小。在 JDK 1.8中移除整個永久代,取而代之的是一個叫元空間的區域。

默認的Eden : from : to = 8 : 1 : 1 ( 可以通過參數 –XX:SurvivorRatio 來設定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。

在新生代中,並不是每次存活的對象都少於10%,有時候若是存活的對象大於10%,就會想老年代進行空間分配擔保

標記-整理

標記-整理算法也分爲兩步,首先標記不可達的對象,然後存活的對象往一端移動,然後直接清理掉端邊界以外的內存。

老年代中存活率比較高,要是使用複製算法,會大量浪費時間在複製對象上,因此複製算法不適合用在存活率比較高的場景。

標記的存活對象將會被整理,按照內存地址依次排列,而未被標記的內存會被清理掉。如此一來,當我們需要給新對象分配內存時,JVM只需要持有一個內存的起始地址即可,這比維護一個空閒列表顯然少了許多開銷。

不難看出,標記/整理算法不僅可以彌補標記/清除算法當中,內存區域分散的缺點,也消除了複製算法當中,內存減半的高額代價。

不過任何算法都會有其缺點,標記/整理算法唯一的缺點就是效率也不高,不僅要標記所有存活對象,還要整理所有存活對象的引用地址。從效率上來說,標記/整理算法要低於複製算法

關於JVM深入研究以及JVM調優,後面的文章會繼續深入,這篇文章先介紹JVM的內存模型,以及回收算法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章