JVM內存管理機制及參數設置和跟蹤參數

 

  • 運行時數據區域

    • 線程共享內存區

      • Java堆

        • Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱做「GC堆」
        • Java堆內存大小可通過-Xms(最小值)和-Xmx(最大值)參數設置,-Xms爲JVM啓動時申請的最小內存,默認爲操作系統物理內存的1/64但小於1G,-Xmx爲JVM可申請的最大內存,默認爲物理內存的1/4但小於1G,默認當空餘堆內存小於40%時,JVM會增大Heap到-Xmx指定的大小,可通過-XX:MinHeapFreeRation=來指定這個比列;當空餘堆內存大於70%時,JVM會減小Heap的大小到-Xms指定的大小,可通過XX:MaxHeapFreeRation=來指定這個比列,對於運行系統,爲避免在運行時頻繁調整Heap的大小,通常-Xms與-Xmx的值設成一樣
        • 如果在堆中沒有內存完成實例分配,並且堆也無法再擴展時,將會拋出OutOfMemoryError異常
        • 分爲新生代和老年代
          • 新生代
            • 程序新創建的對象一般都是從新生代分配內存,新生代由Eden Space和兩塊相同大小的Survivor Space(通常又稱S0和S1或From和To)構成
            • 可通過-Xmn參數來指定新生代的大小,也可以通過-XX:SurvivorRation來調整Eden Space及Survivor Space的大小比列
          • 老年代
            • 用於存放經過多次新生代GC任然存活的對象,例如緩存對象
            • 新建對象,直接進入老年代情況:
              • 新建的大對象,可通過-XX:PretenureSizeThreshold(單位爲字節,默認爲0)參數來判斷是否爲大對象
              • 大的數組對象,且數組中無引用外部對象。老年代內存等於Java堆內存減去新生代內存
      • 方法區

        • 概述
          • 存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據
          • 方法區分配內存可以不連續,可以動態擴展
          • 垃圾收集行爲在這個區域是比較少出現的,但並非數據進入了方法區就如永久代(-XX:PermSize,設置持久代初始值,一般設置爲物理內存的1/64;-XX:MaxPermSize,設置持久代最大值,一般設置爲物理內存的1/4)的名字一樣「永久」存在。在該區域進行內存回收的主要目的是對常量池的回收和對內存數據的卸載;一般來說這個區域的內存回收效率比起 Java 堆要低得多
          • 當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常
        • 運行時常量池(方法區的一部分)
          • 保存Class文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運行時常量池中
          • 運行時常量池相對於 Class常量池一大特徵就是其具有動態性,Java 規範並不要求常量只能在運行時才產生,也就是說運行時常量池中的內容並不全部來自 Class常量池,Class常量池並非運行時常量池的唯一數據輸入口;在運行時可以通過代碼生成常量並將其放入運行時常量池中
          • 當運行時常量池無法申請到內存時,將拋出OutOfMemoryError異常
    • 線程私有內存區

      • Java虛擬機棧

        • 概述
          • 每一個線程都有一個獨自的Java虛擬機棧,它的生命週期和線程相同
          • 每個方法在執行的同時都會創建一個棧幀(Stack Frame)用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息
          • 每一個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程
          • 如果線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError異常;如果虛擬機棧可以動態擴展,如果擴展時無法申請到足夠的內存,就會拋出OutOfMemoryError異常
        • Java虛擬棧組成

          • 局部變量區
            • 局部變量區被組織爲以一個字長爲單位、從0開始計數的數組,存放了編譯期可知的各種基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它不等同於對象本身,可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置)和returnAddress類型(指向了一條字節碼指令的地址)。類型爲short、byte和char的值在存入數組前要被轉換成int值,其中64位長度的long和double類型的數據會佔用2個局部變量空間(Slot),其餘的數據類型只佔用1個。(在訪問局部變量中的long或double時,只需取出連續兩項的第一項的索引值即可,如某個long值在局部變量區中佔據的索引時3、4項,取值時,指令只需取索引爲3的long值即可)
          • 操作數棧
            • 和局部變量區一樣,操作數棧也被組織成一個以字長爲單位的數組。但和前者不同的是,它不是通過索引來訪問的,而是通過入棧和出棧來訪問的。可把操作數棧理解爲存儲計算時,臨時數據的存儲區域。
            • 演示
              • 代碼
                public static int add(int a,int b){
                int c=0;
                c=a+b;
                return c;
                }​​
              • 演示圖片

              • 具體操作過程
                 0:   iconst_0 // 0壓棧
                 1:   istore_2
                // 彈出int,存放於局部變量2
                 2:   iload_0  //
                把局部變量0壓棧
                 3:  
                iload_1 // 局部變量1壓棧
                 4:   iadd      //彈出2個變量,求和,結果壓棧
                 5:   istore_2
                //彈出結果,放於局部變量2
                 6:   iload_2  //局部變量2壓棧
                 7:   ireturn  
                //返回
          • 幀數據區
            除了局部變量區和操作數棧外,java棧幀還需要一些數據來支持常量池解析、正常方法返回以及異常派發機制。這些數據都保存在java棧幀的幀數據區中。
            當JVM執行到需要常量池數據的指令時,它都會通過幀數據區中指向常量池的指針來訪問它。
            除了處理常量池解析外,幀裏的數據還要處理java方法的正常結束和異常終止。如果是通過return正常結束,則當前棧幀從Java棧中彈出,恢復發起調用的方法的棧。如果方法有返回值,JVM會把返回值壓入到發起調用方法的操作數棧。
            爲了處理java方法中的異常情況,幀數據區還必須保存一個對此方法異常引用表的引用。當異常拋出時,JVM給catch塊中的代碼。如果沒發現,方法立即終止,然後JVM用幀區數據的信息恢復發起調用的方法的幀。然後再發起調用方法的上下文重新拋出同樣的異常。
        • 特性
          • 棧上分配
            • 小對象(一般幾十個bytes),在沒有逃逸的情況下,可以直接分配在棧上
            • 直接分配在棧上,可以自動回收,減輕GC壓力
            • 大對象或者逃逸(引用)對象無法棧上分配
      • 本地方法棧

        • 本地方法棧(Native Method Stack)與虛擬機棧所發揮的作用是非常相似的,它們之間的區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則爲虛擬機使用到的Native方法服務
        • 本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常
      • 程序計數器

        • 當前線程所執行字節碼的行號指示器
        • 線程執行Java方法,計數器記錄的是正在執行的虛擬機字節碼指令地址;若線程執行的是本地方法(Native Method),計數器的值爲空(Undefined)
        • 該內存區域是唯一一個在Java虛擬機規範中沒有規定OutOfMemoryError的區域
  • 直接內存

    • 直接內存並不是虛擬機內存的一部分,也不是Java虛擬機規範中定義的內存區域
    • 在JDK 1.4中新加入了NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可以使用Native函數庫直接分配堆外內存,然後通過一個存儲在Java堆中的DirectByteBuffer對象作爲這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因爲避免了在Java堆和Native堆中來回複製數據
    • 當各個內存區域總和大於物理內存,也會拋出OutOfMemoryError
  • 常用JVM跟蹤配置參數

    • Trace跟蹤參數

      • -verbose:gc -XX:+printGC
        • 可以打印GC的簡要信息
          –[GC4790K->374K(15872K), 0.0001606 secs]
          –[GC4790K->374K(15872K), 0.0001474 secs]
      • -XX:+PrintGCDetails
        • 打印GC詳細信息

      • -XX:+PrintGCTimeStamps
        • 打印GC發生的時間戳
          –[GC[DefNew: 4416K->0K(4928K), 0.0001897 secs] 4790K->374K(15872K), 0.0002232 secs]
          ​ [Times: user=0.00 sys=0.00, real=0.00 secs] 
      • -Xloggc:log/gc.log
        • 1、指定GC log的位置,以文件輸出。2、幫助開發人員分析問題
      • -XX:+PrintHeapAtGC
        • 每一次GC過後,都打印堆信息

      • -XX:+TraceClassLoading
        • 監控類的加載

      • -XX:+PrintClassHistogram
        • 按下Ctrl+Break後,打印類的信息:

        • 分別顯示:序號、實例數量、總大小、類型
    • 堆的分配參數

      • -Xmx –Xms
        • 指定最大堆和最小堆
      • -Xmn
        • 設置新生代大小
      • -XX:NewRatio
        • 新生代(eden+2*s)和老年代(不包含永久區)的比值
          4 表示 新生代:老年代=1:4,即年輕代佔堆的1/5
      • -XX:SurvivorRatio
        • 設置兩個Survivor區和eden的比
          8表示 兩個Survivor :eden=2:8,即一個Survivor佔年輕代的1/10
      • -XX:+HeapDumpOnOutOfMemoryError
        • OOM時導出堆到文件
      • -XX:+HeapDumpPath
        • 導出OOM的路徑
      • -XX:OnOutOfMemoryError
        • 在OOM時,執行一個腳本

      • 總結
        • 根據實際情況調整新生代和倖存代的大小
        • 官方推薦新生代佔堆的3/8
        • 倖存代佔新生代的1/10
        • 在OOM時,記得Dump出堆,確保可以排查現場問題
    • 永久區分配參數

      • -XX:PermSize  -XX:MaxPermSize
        • 設置永久區的初始空間和最大空間
        • 表示一個系統可以容納多少個類型
    • 棧大小分配

      • -Xss
        • 通常只有幾百K
        • 決定了函數調用的深度
        • 每個線程都有獨立的棧空間
        • 局部變量、參數分配在棧上