JVM基礎回顧記錄(一):JVM的內存模型

1、JAVA程序執行流程

JAVA程序執行的基本流程(基於HotSpot):java

圖1算法

2、內存模塊劃分

2.1:程序計數器

程序計數器是一塊較小的內存空間,是當前線程執行字節碼的行號指示器,字節碼解釋器就是經過改變這個計數器的值來獲取下一條須要執行的字節碼指令,其中分支、循環、跳轉和異常處理,線程恢復等基礎功能均須要依賴該計數器完成。因爲jvm的多線程是經過線程輪流切換並分配CPU執行時間的方式實現,在任什麼時候刻,一個CPU都只會執行其中一條線程裏的指令,爲了使線程發生切換後能夠順利的定位到上次發生切換時的執行位置,每一個線程都有一個獨立的程序計數器,每條線程的計數器相互獨立,這塊存儲區域被稱爲線程私有內存。多線程

2.2:方法區

也是線程共享的一塊區域,這塊區域主要用來存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據,HotSpot虛擬機實現該區域時採用堆的一塊區域實現,目的是爲了讓GC分代收集擴展至方法區(好比類卸載時是須要GC參與的),所以在堆空間裏劃分出一個代來存儲方法區裏的內容,這塊區域一般被稱做「永久代」,該塊區域大小經過-XX:MaxPermSize來設置(所以可能會出現OOM的狀況),不過須要說明的是,J8已經沒有這塊區域了,並且J7的時候已經將常量池由該塊區域轉移到實際的堆內存裏了(實驗證實,J7和J8的時候,常量池已經被存進了實際的堆內存,但區別是J7的類信息等還放在永久代中)。J8有了元數據存儲,已經完全去除了永久代的概念,具體瞭解:Java8內存劃分jvm

2.3:堆

jvm管理的最大一塊區域,jvm啓動時建立,用來存放對象實例,幾乎全部的對象都在這裏分配內存,經過-Xmx-Xms控制其大小。這塊區域是GC管理的主要區域,從內存回收角度來看,如今的內存回收基本都採用分代收集算法,因此該區域還能夠細分(如圖1堆空間細分),進一步劃分的目的是爲了更好的回收內存或更快的分配內存。這裏列一下例子裏用到的參數(也是比較經常使用的參數):xss

名稱 含義
-xms 堆初始化大小
-xmx 堆最大大小(通常來講,-xms和-xmx設置大小一致,以免每次垃圾回收完成後JVM從新分配內存)
-xmn 新生代大小(結合圖1理解),這裏須要提一下-XX:newSize、-XX:MaxnewSize這倆參數,第一個是指新生代初始大小,第二個是指新生代最大大小,一樣爲了不JVM從新分配內存,這倆數值通常設置爲同樣的,因此jdk4出來了-xmn,一個配置,能夠對上述倆參數同時生效
-XX:NewRatio 新生代(Eden+2Survivors)與老年代的大小比值,若是值爲2,則新生代:老年代=1:2(若是設置了-xmn指定了新生代大小,則無需設置此項,兩個都設置,只生效一個)
-XX:SurvivorRatio 新生代中,Eden區與兩個Survivor的大小比值,若是設置爲4,則eden:survivor1:survivor2 = 4:1:1)
-xss 棧大小,通常默認128k,若是調用棧不是很深(好比很深的遞歸程序),保持默認便可

 表1spa

2.3.1:堆配置詳解

如今讓咱們把圖1中的「堆空間細分」部分放大,結合下面的配置信息和表1中的含義(-Xss和永久代不會體如今圖裏),來用圖說明下:操作系統

配置1:-Xmx3072m  -Xms3072m  -Xmn2g -XX:SurvivorRatio=4  -Xss128k.net

上面的配置表示堆區總大小爲3550M,新生代大小爲2G(2048M,這裏只是說明問題才配置這麼大,實際生產中,新生代要小於老年代),新生代Eden區和兩個Survivor區的比例爲4:1:1,用圖來直觀的表達一下這個配置:線程

圖2指針

配置2:-Xmx3072m  -Xms3072m  -XX:NewRatio=2 -XX:SurvivorRatio=4  -Xss128k

上面的配置表示堆區總大小爲3550m,新生代:老年代=1:2(HotSpot默認),新生代eden區和兩個survivor區的比例爲4:1:1,用圖來直觀的表達一下這個配置:

 

圖3

 

2.4:虛擬機棧

也就是常說的棧,線程私有,生命週期與線程相同,虛擬機棧用來描述java方法(java method)執行的內存模型,每一個方法執行時都會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接、方法出口等信息,一個方法的調用到其完成調用對應一個棧幀在虛擬機的入棧到出棧。

局部變量表裏存放着編譯期可知的基本數據類型、對象引用Reference(多是指向實例對象地址的一個引用指針,也多是表明實例對象的一個句柄)、以及returnAddress(指向一條字節碼的執行命令的地址)。在此區域,若是線程的請求深度大於虛擬機容許的深度,將會拋出StackOverFlowError異常,這個能夠經過一個無限制遞歸或者遞歸深度設置一個很大的數來證實:

public class Recursion {

    public static int i = 0;

    public static void main(String[] args) {
        Recursion t = new Recursion();
        t.recursion();
        System.out.println("程序正常結束~");
    }

    public void recursion(){
        i++;
        if(i > 30000){ //這裏作適當調整,減少則不報棧溢出異常,擴大(好比這裏的3w)就會報棧溢出
            return;
        }
        recursion();
    }
}

看註釋那裏調整便可證實。棧大小經過-Xss參數來調整,須要注意的是,這個參數是對每一個線程生效的(棧幀),通常狀況下,這個設置的值越小,支持建立的線程數就越多(並不是能夠無限多,最終受操做系統限制,操做系統針對每一個進程也有個最大線程數的限制),棧裏存儲的大部分數據,都會隨着棧幀的結束(即method調用完成)而被回收,因此通常遞歸更容易形成棧溢出問題。

2.5:本地方法棧

意義相似虛擬機棧,只不過本地方法棧服務於本地native方法調用(JNI),咱們經常使用的虛擬機HotSpot已將該區和虛擬機棧作了合併。

相關文章
相關標籤/搜索