1. 程式人生 > >網際網路技術學習27———JVM各組成及相關引數

網際網路技術學習27———JVM各組成及相關引數

虛擬機器是一款軟體,用來執行一系列虛擬計算機指令,虛擬機器可以分為系統虛擬機器(如VirtualBox、Vmware)和程式虛擬機器(如Java虛擬機器)。系統虛擬機器是對物理計算機的模擬,提供了一個可以執行完成作業系統的軟體平臺。程式虛擬機器專門為執行單個計算機程式而設計,如在java虛擬機器中執行的指令為java位元組碼指令。java發展至今,出現過很多虛擬機器,最初使用的是Classic的虛擬機器,到現在應用最管飯的是HotSpot虛擬機器。

Java虛擬機器的基本結構

類載入子系統、方法區、java堆、直接記憶體、java棧、本地方法棧、垃圾回收系統、pc暫存器、執行引擎

  1. 類載入子系統:負責從檔案系統或網路中載入calss資訊,載入的資訊存放在一款稱之為方法區的記憶體空間,被所有執行緒共享

  2. 方法區:存放類資訊、常量資訊、常量池資訊,包括字串字面量和數字常量等,被所有執行緒共享。平時,說到永久帶(PermGen space)的時候往往將其和方法區不加區別。這麼理解在一定角度也說的過去。 因為,《Java虛擬機器規範》只是規定了有方法區這麼個概念和它的作用,並沒有規定如何去實現它。那麼,在不同的 JVM 上方法區的實現肯定是不同的了。 同時,大多數用的JVM都是Sun公司的HotSpot。在HotSpot上把GC分代收集擴充套件至方法區,或者說使用永久代來實現方法區。

      雖然可以牽強的解釋這種將方法區和永久帶等同對待觀點。但最終方法區和永久帶還是不同的。一個是標準一個是實現。這就相當於你將java中的介面和介面的實現等同對待了一樣

  3. java堆:在java虛擬機器啟動的時候建立java堆,它是java程式最主要的記憶體工作區域,幾乎所有的物件例項都放在java堆中(例如:部分較小的物件可能放在TLAB中),被所有執行緒共享。

  4. 直接記憶體:java的NIO庫允許ava程式使用直接記憶體,從而提高效能,通常直接記憶體速度 優於java堆,在讀寫頻繁的場合可能考慮使用

  5. java棧:每個虛擬機器執行緒都有一個私有的棧,一個執行緒的java棧線上程建立時被建立,java棧中儲存著區域性變數、方法引數、java的方法呼叫、返回值等。

  6. 本地方法棧:與java棧非常類似,最大不同為本地方法棧用於本地方法呼叫,本地方法棧服務的物件是JVM執行的native方法,而虛擬機器棧服務的是JVM執行的java方法。如何去服務native方法?native方法使用什麼語言實現?怎麼組織像棧幀這種為了服務方法的資料結構?虛擬機器規範並未給出強制規定,因此不同的虛擬機器實可以進行自由實現,我們常用的HotSpot虛擬機器選擇合併了虛擬機器棧和本地方法棧。

  7. 垃圾收集系統:它是java的核心,也是必不可少的,java有一套自己進行垃圾回收的機制,開發人員無需手工清理

  8. 8pc暫存器:它是每個執行緒私有的空間,java虛擬機器會為每個執行緒建立pc暫存器,在任意時刻,一個java執行緒總是在執行一個方法,這個方法被稱為當前方法,如果當前方法不是本地方法,pc暫存器就會執行當前正在被執行的指令,如果是本地方法,則pc暫存器值為undefined,暫存器存放如房錢執行環境指標、程式計數器、操作棧指標、計算的變數指標等資訊。

  9. 執行引擎:它是虛擬機器最核心元件,複製執行虛擬機器的位元組碼。一般先進行變異成機器碼,執行引擎對機器碼進行執行。

Java堆

堆解決的是資料儲存的問題,即資料怎麼放,放在那裡。棧解決的程式執行的問題,即程式如何執行,或者說如何處理資料。方法區則是輔助堆疊的快永久區,解決堆疊資訊的生產,是先決條件。若我們建立一個新的User類的物件user,則User類的資訊及靜態資訊存放在方法區中,新建立的user物件存放在java堆中,當我們去使用的時候回,都是用用物件的引用user,這個user放在java棧中。

  判斷新生代和老年代是通過物件經歷的垃圾回收的次數

  java堆是java應用程式關係最密切的記憶體空間,幾乎所有的物件都是放在其中。並且堆是通過垃圾回收機制完全自動化管理的。

  根據垃圾回收機制的不同,java堆可能有不同的結構。最為常見的就是將整個java堆分成新生代和老年代,其中新生代存放新生的物件或者年齡不大的物件,老年代存放老年物件。新生代分為eden區(伊甸區)、s0區、s1區,其中s0和s1也被稱為form區和to區,他們是兩塊大小相等並且可以角色互換的空間。絕大多數情況下,物件首先分配在eden區,在一次新生代回收後,如果物件還活著,則會進入s0或者s1區,之後每經過一次新生代回收,如果物件存活則它的年齡增加1,當物件達到一定年齡後,則進入老年代。

  新生代的from和to空間使用的垃圾收集演算法是複製演算法,複製演算法的核心就是將記憶體空間分為兩塊,每次只使用其中的一塊,在垃圾回收時,將正在使用的記憶體中的物件複製到未被使用的記憶體塊中,之後清除之前正在使用的記憶體塊中所有物件,反覆去交換這兩個記憶體塊的角色,完成垃圾回收。

  老年代使用的垃圾收集演算法是標記壓縮法,標記壓縮法在標記清除法的基礎上做了優化,把存活的物件壓縮到記憶體的一端,而後進行垃圾回收。標記的過程是用過GcRoots可達性判斷的。

  新生代和老年代都會經歷GC,區別是GC頻率不同。新生代GC是比較頻繁的,而老年代是經歷多次GC知乎仍然儲存下來的,已經比較穩定,老年代gc沒有那麼頻繁,

java棧

  java棧是一塊執行緒私有的記憶體空間,一般分為三部分:區域性變量表、運算元棧。幀資料區

  1. 區域性變量表:用於儲存函式的引數及區域性變數
  2. 運算元棧:儲存計算過程的中間結果,同時作為計算過程中變數臨時的儲存空間
  3. 幀資料區:除了區域性變量表和運算元棧之外,棧還需要一些資料還支援常量池的解析,這裡幀資料區儲存著訪問常量池的指標,方便程式訪問常量池。另外,當函式返回或者出現異常時,虛擬機器必須有一個異常處理表,方便發現異常的的時候找到異常的程式碼,異常處理表也是幀資料區的一部分。

虛擬機器引數設定

在虛擬機器執行過程中,如果可以跟蹤系統的執行狀態,那馬對於文藝的故障排查會有一定的幫助,為此,虛擬機器提供了一些跟蹤系統狀態的引數,使用給定的引數執行java虛擬機器,就可以在系統執行時列印相關日誌,用於分析實際問題。進行虛擬機器引數配置,主要圍繞著堆、棧。方法區進行設定。

 堆分配引數

-XX:+PRINTGC  使用這個引數,虛擬機器啟動後,只要遇到GC就會列印日誌

-XX:+UseSerialGC 配置序列垃圾回收器

-XX:+PrintGCDetails  可以檢視詳細資訊,包括各個區的情況

-Xms:  設定java程式啟動時初始化堆大小

-Xmx:  設定java程式能獲得的最大堆大小

 

-Xmx20m -Xms5m -XX:+PrientCommandLineFlags   初始化堆大小5m,最大堆大小20m,將顯示的輸出傳給虛擬機器的配置引數

在實際工作中,可以直接將初始堆大小設定相等,這樣可以減少程式執行時垃圾回收次數,從而提高效能。

在idea中配置jvm引數

在eclipse中配置jvm引數

Run as -- Run Configurations ,在Main選項卡中設定project、main class;在Arguments中,Program arguments部分可以給main主函式傳遞一些引數,VM arguments可以設定JVM引數。

Test01.java

package com.jvmTest;

/**
 * Created by BaiTianShi on 2018/9/20.
 * 測試gc列印
 */
public class Tesst01 {
    public static void main(String[] args) {
        System.out.println("max memory:"+Runtime.getRuntime().maxMemory());
        System.out.println("total memory:"+Runtime.getRuntime().totalMemory());
        System.out.println("free memory:"+Runtime.getRuntime().freeMemory());

        byte[] b = new byte[1024*1024];
        System.out.println("分配了1m");
        System.out.println("max memory:"+Runtime.getRuntime().maxMemory());
        System.out.println("total memory:"+Runtime.getRuntime().totalMemory());
        System.out.println("free memory:"+Runtime.getRuntime().freeMemory());

        byte[] b4 = new byte[1024*1024*4];
        System.out.println("分配了4M");
        System.out.println("max memory:"+Runtime.getRuntime().maxMemory());
        System.out.println("total memory:"+Runtime.getRuntime().totalMemory());
        System.out.println("free memory:"+Runtime.getRuntime().freeMemory());
    }
}

配置的引數:

-Xms5m -Xmx20m -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintCommandLineFlags

列印結果

-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 
max memory:20316160
total memory:6094848
free memory:4846128
[GC (Allocation Failure) [DefNew: 1219K->192K(1856K), 0.0047945 secs] 1219K->660K(5952K), 0.0049086 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
分配了1m
max memory:20316160
total memory:6094848
free memory:4337080
[GC (Allocation Failure) [DefNew: 1247K->0K(1856K), 0.0035955 secs][Tenured: 1684K->1684K(4096K), 0.0051267 secs] 1716K->1684K(5952K), [Metaspace: 3180K->3180K(1056768K)], 0.0088664 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
分配了4M
max memory:20316160
total memory:10358784
free memory:4405072
Heap
 def new generation   total 1920K, used 51K [0x00000000fec00000, 0x00000000fee10000, 0x00000000ff2a0000)
  eden space 1728K,   2% used [0x00000000fec00000, 0x00000000fec0cc50, 0x00000000fedb0000)
  from space 192K,   0% used [0x00000000fedb0000, 0x00000000fedb0000, 0x00000000fede0000)
  to   space 192K,   0% used [0x00000000fede0000, 0x00000000fede0000, 0x00000000fee10000)
 tenured generation   total 8196K, used 5780K [0x00000000ff2a0000, 0x00000000ffaa1000, 0x0000000100000000)
   the space 8196K,  70% used [0x00000000ff2a0000, 0x00000000ff8452f0, 0x00000000ff845400, 0x00000000ffaa1000)
 Metaspace       used 3186K, capacity 4494K, committed 4864K, reserved 1056768K
  class space    used 348K, capacity 386K, committed 512K, reserved 1048576K

Process finished with exit code 0

 

 

-Xmn: 設定新生代的大小,若設定的新生代比較大會減小老年代的大小,這個引數對系統性能以及Gc行為有很大影響,新生代大小一般會設定為整個堆空間的1/4到1/3左右。

-XX:ServivorRatio:用來設定新生代中eden空間和from/to的空間比例,即 eden/from = eden/to

不同的堆分佈情況,對系統執行會產生一定的影響,在實際工作中,應該根據系統的特點做出合理的配置。儘可能將物件預留在新生代,減少老年代的GC次數。

Test02.java

package com.jvmTest;

/**
 * Created by BaiTianShi on 2018/9/21.
 */
public class Test02 {

    public static void main(String[] args) {
        byte[] b = null;
        for (int i = 0; i < 10 ; i++) {
            b = new byte[1024*1024];
        }
    }

}

JVM引數
-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:UserSerialGC -XX:+PrintGCDetails

-XX:NewRatio  設定老年代和新生代的比,即老年代/新生代

繼續使用Test02.java

JVM引數

-Xms20m -Xmx20m -XX:NewRatio=2  -XX:+UseSerialGC  -XX:+PrintGCDetails

 

堆溢位處理

在java程式的執行過程中,如果堆空間不足,則會丟擲記憶體溢位的錯誤(Out Of Memory) oom,一旦這類問題發生在生產環境,可能引起嚴重的業務中斷,可以考慮使用下面的兩個引數

-XX:+HeapDumpOnOutOfMemoryError   可以在記憶體溢位時匯出整個堆資訊

-XX:HeapDumpPath   設定匯出堆的存放路徑

Test03.java

package com.jvmTest;

import java.util.Vector;

/**
 * Created by BaiTianShi on 2018/9/21.
 */
public class Test03 {
    public static void main(String[] args) {
        Vector v = new Vector();
        for (int i = 0; i < 5; i++) {
            v.add(new byte[1024*1024]);
        }
    }
}

JVM引數

-Xms1嗎-Xmx1m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d/test03.dump

控制檯列印及輸出的檔案

使用記憶體分析工具Memory Analyzer可進行記憶體情況分析

 

棧配置

Java虛擬機器提供了引數-Xss來指定執行緒的最大棧空間,該引數直接決定了函式可呼叫的最大深度

Test04.java

package com.jvmTest;

/**
 * Created by BaiTianShi on 2018/9/21.
 */
public class Test04 {
    //棧呼叫深度
    private static int count =0;

    public static void recursion() {
        count++;
        recursion();
    }

    public static void main(String[] args) {

        try {
            recursion();
        } catch (Throwable  e) {
            System.out.println("掉呼叫最大深度:"+count);
            e.printStackTrace();
        }
    }
}

JVM引數

-Xss1m

 

方法區設定

  JDK1.8之後,方法區中的永久帶PermGen被移除,替換為元空間MateSpace,其實在JDK1.7就已經著手做這件事了,把永久帶中的部分資料轉移到本地方法棧和虛擬機器棧中了,直到1.8之後才完全移除。

  元空間的本質和永久代類似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機器中,而是使用本地記憶體。因此,預設情況下,元空間的大小僅受本地記憶體限制,但可以通過以下引數來指定元空間的大小:

  -XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行型別解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。
  -XX:MaxMetaspaceSize,最大空間,預設是沒有限制的。

  除了上面兩個指定大小的選項以外,還有兩個與 GC 相關的屬性:
  -XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少為分配空間所導致的垃圾收集
  -XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少為釋放空間所導致的垃圾收集

 

直接記憶體配置

  直接記憶體也是java程式中非常重要的一部分,特別是廣泛應用在NIO中,直接跳過java堆,是java程式可以直接訪問原生堆空間,因此在一定程度桑加快了記憶體空間的訪問速度。但是說直接記憶體一定就可以提高記憶體訪問熟讀也不見得,具體情況具體分析。

  相關配置引數:-XX:MaxDirectMemorySize,如果不設定,則預設為最大堆空間-Xmx。直接記憶體使用達到上限時,就會觸發垃圾回收,如果不能有效釋放空間,也會引起系統OOM

Client和Server虛擬機器工作模式

目前java虛擬機器支援Client、Server兩種模式,使用-client可以指定使用Client模式,使用-server可以指定使用Server模式。可以直接在命令列中檢視當前計算機系統自動選擇的執行模式,即:java-version

二者區別:Client模式相對Server模式啟動較快沒如果不追求系統長時間使用效能,而僅僅是測試,可以使用Client模式。而Server模式則啟動比較慢,原因是會對其進行復雜的系統性能資訊收集和使用更發展的演算法對程式進行優化,一般在生產環境下都會使用Server模式,長期執行其效能要遠遠快於Client模式。但是64為貌似沒有Client模式,摸索中。