JVM內存模型解析

引言

JVM內存模型能夠分爲兩個部分,以下圖所示,堆和方法區是全部線程共有的,而虛擬機棧,本地方法棧和程序計數器則是線程私有的。下面咱們就來一一分析一下這些不一樣區域的做用。java

jvm-memeory-model

JVM系列文章

JVM垃圾回收算法及回收器詳解
JVM類加載機制詳解git

堆內存

堆內存是全部線程共有的,能夠分爲兩個部分:年輕代和老年代。下圖中的Perm表明的是永久代,可是注意永久代並不屬於堆內存中的一部分,同時jdk1.8以後永久代也將被移除。github

f07367b4cac67f6479a3b0be1ea85bcf

GC(垃圾回收器)對年輕代中的對象進行回收被稱爲Minor GC,用通俗一點的話說年輕代就是用來存放的年輕的對象,年輕對象是什麼意思呢?年輕對象能夠簡單的理解爲沒有經歷過屢次垃圾回收的對象,若是一個對象經歷過了必定次數的Minor GC,JVM通常就會將這個對象放入到年老代,而JVM對年老代的對象的回收則稱爲Major GC。算法

如上圖所示,年輕代中還能夠細分爲三個部分,咱們須要重點關注這幾點:jvm

  1. 大部分對象剛建立的時候,JVM會將其分佈到Eden區域。
  2. 當Eden區域中的對象達到必定的數目的時候,就會進行Minor GC,經歷此次垃圾回收後全部存活的對象都會進入兩個Suvivor Place中的一個。
  3. 同一時刻兩個Suvivor Place,即s0和s1中總有一個老是空的。
  4. 年輕代中的對象經歷過了屢次的垃圾回收就會轉移到年老代中。
  5. 當申請不到空間時會拋出 OutOfMemoryError。下面咱們簡單的模擬一個堆內存溢出的狀況:
import java.util.ArrayList;
import java.util.List;
/* java -Xms20m -Xmx20m HeapOOM */
public class HeapOOM {
    static class OOMObject {
    }
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

下面是執行結果:性能

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:261)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
    at java.util.ArrayList.add(ArrayList.java:458)
    at HeapOOM.main(HeapOOM.java:13)

堆內存是咱們平時在生產環境中進行性能調優中的一個很是重要的部分,對於這裏我在另一篇文章JVM垃圾回收算法及回收器詳解有詳細介紹,這裏咱們仍是拓展補充幾個常見的性能調優參數:優化

  • PretenureSizeThreshold: 直接晉升到老年代的對象大小,設置這個參數後,大於這個參數的對象將直接在老年代分配。
  • MaxTenuringThrehold: 晉升到老年代的對象年齡。每一個對象在堅持過一次Minor GC以後,年齡就會加1,當超過這個參數值時就進入老年代。
  • UseAdaptiveSizePolicy: 動態調整Java堆中各個區域的大小以及進入老年代的年齡。
  • SurvivorRattio: 新生代Eden區域與Survivor區域的容量比值,默認爲8,表明Eden: Suvivor= 8: 1。
  • NewRatio: 設置新生代(包括Eden和兩個Survivor區)與老年代的比值(除去持久代),設置爲3,則新生代與老年代所佔比值爲1:3,新生代佔整個堆棧的1/4。
  • Xmx: 設置JVM堆最大內存。
  • Xms: 設置JVM堆初始內存。
  • Xmn: 參數設置了年輕代內存。

方法區

方法區與Java堆同樣,是各個線程共享的區域,它用於存儲已被虛擬機加載的類信息,常量,靜態變量,即時編譯(JIT)後的代碼等數據。spa

對於JDK1.8以前的HotSpot虛擬機而言,不少人常常將方法區稱爲咱們上圖中所描述的永久代,實際上二者並不等價,由於這僅僅是HotSpot的設計團隊選擇利用永久代來實現方法區而言。同時對於其餘虛擬機好比IBM J9中是不存在永久代的概念的。操作系統

其實,移除永久代的工做從JDK1.7就開始了。JDK1.7中,存儲在永久代的部分數據就已經轉移到了Java Heap或者是 Native Heap。但永久代仍存在於JDK1.7中,並沒徹底移除,譬如符號引用(Symbols)轉移到了native heap;字面量(interned strings)轉移到了java heap;類的靜態變量(class statics)轉移到了java heap,而在JDK1.8以後永久代的該概念也已經再也不存在取而代之的是元空間metaspace。線程

常量池實際上是方法區中的一部分,由於這裏比較重要,因此咱們拿出來單獨看一下。注意咱們這裏所說的運行時的常量池並僅僅是指Class文件中的常量池,由於JVM可能會進行即時編譯進行優化,在運行時將部分常量載入到常量池中。

程序計數器

JVM中的程序計數器和計算機組成原理中提到的程序計數器PC概念相似,是線程私有的,用來記錄當前執行的字節碼位置。仍是稍微解釋一下吧,CPU的佔有時間是以分片的形式分配給給每一個不一樣線程的,從操做系統的角度來說,在不一樣線程之間切換的時候就是依賴程序計數器來記錄上一次線程所執行到具體的代碼的行數,在JVM就是字節碼。

Java虛擬機棧

與程序計數器同樣,Java虛擬機棧也是線程私有的,用通俗的話將它就是咱們經常據說到堆棧中的那個「棧內存」。虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀(Stack Frame)用於存儲局部變量表(局部變量表須要的內存在編譯期間就肯定了因此在方法運行期間不會改變大小),操做數棧,動態連接,方法出口等信息。每個方法從調用至出棧的過程,就對應着棧幀在虛擬機中從入棧到出棧的過程。p.s: 關於棧幀這裏咱們之後講虛擬機字節碼執行引擎的時候再來仔細分析。

本地方法棧

本地方法棧和Java虛擬機棧相似,只不過是爲JVM執行Native方法服務,這裏就不解釋了。

Contact

GitHub: https://github.com/ziwenxie
Blog: https://www.ziwenxie.site

本文爲做者原創,轉載請於開頭明顯處聲明我的博客出處 :-)

相關文章
相關標籤/搜索