JVM精進之路

1、Java虛擬機結構java

一、Java Virtual Machine:JVM是運行在操做系統之上,與硬件沒有直接編程

二、JVM體系結構緩存

 

 

 三、類裝載器ClassLoader服務器

  負責加載class文件,class文件在文件開頭有特定的文件標示,而且ClassLoader只負責class文件的加載,至於它是否能夠運行,則由Exection Engine(執行引擎負責解釋命令,提交操做系統執行)決定。多線程

  通常:Java 運行代碼時,JVM 的整個處理過程以下併發

  ①.JVM 向操做系統申請內存,JVM 第一步就是經過配置參數或者默認配置參數向操做系統申請內存空間,根據內存大小找到具體的內存分配表,而後把內存段的起始地址和終止地址分配給 JVM,接下來 JVM 就進行內部分配。編程語言

  ②.JVM 得到內存空間後,會根據配置參數分配堆、棧以及方法區的內存大小。函數

  ③.class 文件加載、驗證、準備以及解析,其中準備階段會爲類的靜態變量分配內存,初始化爲系統的初始值。高併發

  ④. 完成上一個步驟後,將會進行最後一個初始化階段。在這個階段中,JVM 首先會執行構造器 <clinit> 方法,編譯器會在.java 文件被編譯成.class 文件時,收集全部類的初始化代碼,包括靜態變量賦值語句、靜態代碼塊、靜態方法,收集在一塊兒成爲 <clinit>() 方法。性能

 

   類加載過程:

 

 

   

 

 

3.1 Class Loader SubSystem

 

3.1.1 Load

 

 

 

 雙親委派:某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,依次遞歸,若是父類加載器能夠完成類加載任務,就成功返回;只有父類加載器沒法完成此加載任務時,才本身去加載。

3.1.2 Link

3.1.3 Init初始化

標記爲常量值的字段賦值,以及執行<clinit>方法的過程。JVM會經過加鎖來確保類的<clinit>方法僅被執行一次。只有當初始化完成以後,類才正式成爲可執行的狀態。父類型的初始化邏輯優先於當前類的邏輯。

除被final所修飾的靜態字段,剩下的直接賦值操做,以及全部靜態代碼塊中的代碼,會被Java編譯器至於同一方法中,命名<clinit>。

類初始化被觸發的狀況:

  ①當虛擬機啓動時,初始化用戶指定的主類;

  ②當遇到用以新建目標類實例的new指令時,初始化new指令的目標類;

  ③當遇到調用靜態方法的指令時,初始化該靜態方法所在的類;

  ④當遇到訪問靜態字段的指令時,初始化該靜態字段所在的類;

  ⑤子類的初始化會觸發父類的初始化;

  ⑥若是一個接口定義了default方法,那麼直接實現或者間接實現該接口的類的初始化,會觸發該接口的初始化;

  ⑦使用反射API對某個類進行反射調用時,初始化這個類;

  ⑧當初次調用MethodHandle實例時,初始化該MethodHandle指向的方法所在的類。

 

四、Native Interface本地接口

  Java語言自己不能對操做系統底層進行訪問和操做,可是能夠經過JNI接口調用其餘語言來實現對底層的訪問。

  本地接口的做用是融合不一樣編程語言爲Java所用,它的初衷是融合 C/C++程序,Java誕生的時候是C/C++橫行的時候,要想立足,必須有調用C/C++程序,因而就在內存中專門開闢了一塊區域處理標記爲Native的代碼,它的具體作法是Native Method Stack中登記Native方法,在Execution Engine 執行時加載Native libraries。

 

五、Native Method Stack

它的具體作法是Native Method Stack中登記native方法,在Execution Engine執行時加載本地方法庫。

 

 六、PC寄存器

  每一個線程都有一個程序計數器,是線程私有的,就是一個指針,指向方法區中的方法字節碼(用來存儲指向下一條指令的地址,也即將要執行的指令代碼),由執行引擎讀取下一條指令,是一個很是小的內存空間,幾乎能夠忽略不記。

七、棧

  棧也叫棧內存,主管Java程序的運行,是在線程建立時建立,它的生命期是跟隨線程的生命期,線程結束棧內存也就釋放,對於棧來講不存在垃圾回收問題,只要線程一結束該棧就Over,生命週期和線程一致,是線程私有的。基本類型的變量、實例方法、引用類型變量都是在函數的棧內存中分配。

  如:Exception: java.lang.StackOverflowError

八、方法區  

  方法區是線程共享的,一般用來保存裝載的類的元結構信息。好比:運行時常量池+靜態變量+常量+字段+方法字節碼+在類/實例/接口初始化用到的特殊方法等。

  一般和永久區關聯在一塊兒(Java7以前),但具體的跟JVM的實現和版本有關。

九、Head堆(Java 7以前)

  一個JVM實例只存在一個堆內存,堆內存大小是能夠調節的。(-Xms1024m -Xmx1024m -XX:+PrintGCDetails)

  類加載器讀取了類文件後,須要把類、方法、常變量放到堆內存中,保存全部引用類型的真實信息,以方便執行器執行。

 

 

十、新生區
  新生區是類的誕生、成長、消亡的區域,一個類在這裏產生,應用,最後被垃圾回收器收集,結束生命。
  新生區又分爲兩部分: 伊甸區(Eden space)和倖存者區(Survivor pace)。
  Eden space:全部的類都是在伊甸區被new出來的。
  Survivor pace: 0區(Survivor 0 space)和1區(Survivor 1 space)。
  當Eden space的空間用完時,程序又須要建立對象,JVM的垃圾回收器將對Eden space進行垃圾回收(Minor GC),將Eden space中的再也不被其餘對象所引用的對象進行銷燬。而後將Eden space中的剩餘對象移動到倖存0區。若倖存0區也滿了,再對該區進行垃圾回收,而後移動到1區。那若是1區也滿了,再移動到養老區。若養老區也滿了,那麼這個時候將產生MajorGC(FullGC),進行養老區的內存清理。若養老區執行了Full GC以後發現依然沒法進行對象的保存,就會產生OOM異常「OutOfMemoryError」。

  若是出現java.lang.OutOfMemoryError: Java heap space異常,說明Java虛擬機的堆內存不夠。緣由有二:
  (1)Java虛擬機的堆內存設置不夠,能夠經過參數-Xms、-Xmx來調整。
  (2)代碼中建立了大量大對象,而且長時間不能被垃圾收集器收集(存在被引用)。

2、內存溢出

一、stackOverflowError(棧溢出)

線程私有:

  ①Program Counter Register(PC寄存器):不存在。

  ②Java Stack:對於不可動態擴展棧大小的線程來講,不斷持續的往棧裏面壓入棧幀,最後就會發生stackOverflowError

  ③Native Method Stack:本地方法棧調用本地方法接口,進而調用本地方法庫(C或C++編寫),也會發生stackOverflowError

二、OutOfMemeryError

  2.1 直接內存(Direct Memory)——JVM內存模型外:

  java.lang.OutOfMemeryError:Direct buffer Memery:元空間(jdk8)並不在虛擬機中,而是使用本地內存 。NIO程序使用ByteBuffer來讀取或者寫入,基於通道(Channel)與緩存區(Buffer)的I/O方式,它可使用Native函數庫直接分配堆外內存,而後經過一個存儲在Java堆裏面的DirectByteBuffer對象做爲這塊內存的引用進行操做。這樣能在一些場景中顯著提升性能,避免了在Java堆和Native堆中來回複製數據。

  ByteBuffer.alloteDirect(capability):分配OS本地內存,不屬於GC管轄範圍,因爲不須要內存拷貝因此速度相對較快。若是不斷分配本地內存,但本地內存可能已經使用光了,再次嘗試分配本地內存就會出現OutOfMemeryError,那程序就直接崩潰。

  2.2 JVM內存模型內

  java.lang.OutOfMemeryError:Java heap space:堆內存溢出,最多見。

  java.lang.OutOfMemeryError:PerGen space:(jdk8以前)JVM對永久代回收很是不積極,運行時大量動態類生成、相似Intern字符串緩存佔用太多。

  java.lang.OutOfMemeryError:Metaspace:(jdk8)。

  java.lang.OutOfMemeryError:GC overhead limit exceeded:GC回收時間過長,就會拋出OOM,超過98%的時間用來作GC而且回收不了2%的堆內存,連續過次GC,都回收不了2%的狀況纔會發生。 

  java.lang.OutOfMemeryError:unable to create new native thread:高併發請求服務器,應用建立了太多線程了,一個應用進程建立多個線程,超過系統承載極限;Linux系統默認容許單個進程能夠建立的線程時1024。

    解決辦法:下降應用程序建立線程的數量,不過應用不須要這麼多線程,改代碼將線程數量降下來。不然,修改Linux默認1024個線程的限制,擴大值。

 

3、JVM標配、X和XX參數

https://blog.csdn.net/longgeqiaojie304/article/details/93851827

4、JVM調優

十一、MAT(Eclipse Memory Analyzer)

  Eclipse Markerplace中搜索安裝Memory Analyzer。

  運行時:調出Run Configurations 在Arguments的VM arguments中設置-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError。。。。。。Apply 。。。Run

  刷新項目,出現java_pidxxxxx.hprof。。。。

 

相關文章
相關標籤/搜索