1.什么是JVM?
JVM是Java Virtual Machine(Java虛擬機)的縮寫,是通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的。由一套字節(jié)碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域等組成。JVM屏蔽了與操作系統(tǒng)平臺相關的信息,使得Java程序只需要生成在Java虛擬機上運行的目標代碼(字節(jié)碼),就可在多種平臺上不加修改的運行,這也是Java能夠“一次編譯,到處運行的”原因。
2.JRE、JDK和JVM的關系
- JRE(Java Runtime Environment, Java運行環(huán)境)是Java平臺,所有的程序都要在JRE下才能夠運行。包括JVM和Java核心類庫和支持文件。
- JDK(Java Development Kit,Java開發(fā)工具包)是用來編譯、調(diào)試Java程序的開發(fā)工具包。包括Java工具(javac/java/jdb等)和Java基礎的類庫(java API)
- JVM(Java Virtual Machine, Java虛擬機)是JRE的一部分。JVM主要工作是解釋自己的指令集(即字節(jié)碼)并映射到本地的CPU指令集和OS(操作系統(tǒng))的系統(tǒng)調(diào)用。Java語言是跨平臺運行的,不同的操作系統(tǒng)會有不同的JVM映射規(guī)則,使之與操作系統(tǒng)無關,完成跨平臺性。
3.java 體系結構介紹

jvm體系總體分四大塊:
- 類的加載機制
- jvm內(nèi)存結構
- GC算法 垃圾回收
- GC分析 命令調(diào)優(yōu)
①jvm體系之-類的加載機制
-
什么是類的加載?
類的加載指的是將類的.class文件中的二進制數(shù)據(jù)讀入到內(nèi)存中,將其放在運行時數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個java.lang.Class對象,用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結構。類的加載的最終產(chǎn)品是位于堆區(qū)中的Class對象,Class對象封裝了類在方法區(qū)內(nèi)的數(shù)據(jù)結構,并且向Java程序員提供了訪問方法區(qū)內(nèi)的數(shù)據(jù)結構的接口。 -
加載類的過程。
(1)裝載(loading)
負責找到二進制字節(jié)碼并加載至JVM中,JVM通過類名、類所在的包名、ClassLoader完成類的加載。
因此,標識一個被加載了的類:類名 + 包名 + ClassLoader實例ID。
(2)鏈接(linking)
負責對二進制字節(jié)碼的格式進行校驗、初始化裝載類中的靜態(tài)變量以及解析類中調(diào)用的接口。
鏈接又包含三塊內(nèi)容:驗證、準備、初始化。
1)驗證,文件格式、元數(shù)據(jù)、字節(jié)碼、符號引用驗證;
2)準備,為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認值;
3)解析,把類中的符號引用轉換為直接引用
(3)初始化(initializing)
負責執(zhí)行類中的靜態(tài)初始化代碼、構造器代碼以及靜態(tài)屬性的初始化,以下六種情況初始化過程會被觸發(fā)。
1. 創(chuàng)建類的實例,也就是new的方式
2.訪問某個類或接口的靜態(tài)變量,或者對該靜態(tài)變量賦值
3.調(diào)用類的靜態(tài)方法
4.反射(如Class.forName(“com.shengsiyuan.Test”))
5.初始化某個類的子類,則其父類也會被初始化
6.Java虛擬機啟動時被標明為啟動類的類(Java Test),直接使用java.exe命令來運行某個主類
-
類加載器
站在Java開發(fā)人員的角度來看,類加載器可以大致劃分為以下三類:
(1).啟動類加載器:Bootstrap ClassLoader
負責加載存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,
或被-Xbootclasspath參數(shù)指定的路徑中的
并且能被虛擬機識別的類庫(如rt.jar,所有的java.開頭的類均被Bootstrap ClassLoader加載)。
啟動類加載器是無法被Java程序直接引用的。
(2).擴展類加載器:Extension ClassLoader
該加載器由sun.misc.Launcher$ExtClassLoader實現(xiàn),
它負責加載JDK\jre\lib\ext目錄中,或者由java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫(如javax.開頭的類),
開發(fā)者可以直接使用擴展類加載器。
(3).應用程序類加載器:Application ClassLoader
該類加載器由sun.misc.Launcher$AppClassLoader來實現(xiàn),
它負責加載用戶類路徑(ClassPath)所指定的類,開發(fā)者可以直接使用該類加載器,
如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
應用程序都是由這三種類加載器互相配合進行加載的,如果有必要,我們還可以加入自定義的類加載器。因為JVM自帶的ClassLoader只是懂得從本地文件系統(tǒng)加載標準的java class文件,因此如果編寫了自己的ClassLoader,便可以做到如下幾點:
1、在執(zhí)行非置信代碼之前,自動驗證數(shù)字簽名。
2、動態(tài)地創(chuàng)建符合用戶特定需要的定制化構建類。
3、從特定的場所取得java class,例如數(shù)據(jù)庫中和網(wǎng)絡中。
-
雙親委托模式當類加載器收到了類加載的請求
當JVM加載一個類的時候,下層的加載器會將任務給上一層類加載器,上一層加載檢查它的命名空間中是否已經(jīng)加載這個類,如果已經(jīng)加載,直接使用這個類。如果沒有加載,繼續(xù)往上委托直到頂部。檢查之后,按照相反的順序進行加載。如果Bootstrap加載器不到這個類,則往下委托,直到找到這個類。一個類可以被不同的類加載器加載。
雙親委派機制:
1、當AppClassLoader加載一個class時,它首先不會自己去嘗試加載這個類,
而是把類加載請求委派給父類加載器`ExtClassLoader`去完成。
2、當ExtClassLoader加載一個class時,它首先也不會自己去嘗試加載這個類,
而是把類加載請求委派給`BootStrapClassLoader`去完成。
3、如果BootStrapClassLoader加載失敗(例如在$JAVA_HOME/jre/lib里未查找到該class),
會使用`ExtClassLoader`來嘗試加載;
4、若ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,
如果 `AppClassLoader`也加載失敗,則會報出異常ClassNotFoundException。
雙親委派模型意義:
系統(tǒng)類防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼
保證Java程序安全穩(wěn)定運行
可見性限制:下層的加載器能夠看到上層加載器中的類,反之則不行,委派只能從下到上。
不允許卸載類:類加載器可以加載一個類,但不能夠卸載一個類。但是類加載器可以被創(chuàng)建或者刪除。
②jvm體系之-jvm內(nèi)存結構
根據(jù)《Java 虛擬機規(guī)范(Java SE 7 版)》規(guī)定,Java 虛擬機所管理的內(nèi)存如下圖所示。

方法區(qū)和堆是所有線程共享的內(nèi)存區(qū)域;
而java棧、本地方法棧和程序員計數(shù)器是運行是線程私有的內(nèi)存區(qū)域。
詳細介紹每個區(qū)域的作用
- JAVA堆
對于大多數(shù)應用來說,Java堆(Java Heap)是Java虛擬機所管理的內(nèi)存中的一塊。Java堆是被所有線程
的一塊內(nèi)存區(qū)域,在虛擬機
。此內(nèi)存區(qū)域的唯一目的就是
,幾乎所有的對象實例都在這里分配內(nèi)存。
Java堆是垃圾收集器管理的主要區(qū)域,因此很多時候也被稱做。如果從內(nèi)存回收的角度看,由于現(xiàn)在收集器基本都是采用的分代收集算法,所以Java堆中還可以細分為:新生代和老年代;再細致一點的有Eden空間、From Survivor空間、To Survivor空間等。
根據(jù)Java虛擬機規(guī)范的規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,就像我們的磁盤空間一樣。在實現(xiàn)時,既可以實現(xiàn)成固定大小的,也可以是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現(xiàn)的(通過-Xmx和-Xms控制)。
如果在堆中沒有內(nèi)存完成實例分配,并且堆也無法再擴展時,將會拋出OutOfMemoryError異常。
- 方法區(qū)
方法區(qū)(Method Area)與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域,它用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼}等數(shù)據(jù)。雖然Java虛擬機規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區(qū)分開來。
java7之前,方法區(qū)位于永久代(PermGen),永久代和堆相互隔離,永久代的大小在啟動JVM時可以設置一個固定值,不可變;
java7中,存儲在永久代的部分數(shù)據(jù)就已經(jīng)轉移到Java Heap或者Native memory。但永久代仍存在于JDK 1.7中,并沒有完全移除,譬如符號引用(Symbols)轉移到了native memory;字符串常量池(interned strings)轉移到了Java heap;類的靜態(tài)變量(class statics)轉移到了Java heap。
java8中,取消永久代,方法存放于元空間(Metaspace),元空間仍然與堆不相連,但與堆共享物理內(nèi)存,邏輯上可認為在堆中
根據(jù)Java虛擬機規(guī)范的規(guī)定,當方法區(qū)無法滿足內(nèi)存分配需求時,將拋出OutOfMemoryError異常。
- 程序計數(shù)器
程序計數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它的作用可以看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機的概念模型里(僅是概念模型,各種虛擬機可能會通過一些更高效的方式去實現(xiàn)),字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數(shù)器來完成。
由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內(nèi)核)只會執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復到正確的執(zhí)行位置,每條線程都需要有一個獨立的程序計數(shù)器,各條線程之間的計數(shù)器互不影響,獨立存儲,我們稱這類內(nèi)存區(qū)域為“線程私有”的內(nèi)存。
如果線程正在執(zhí)行的是一個Java方法,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址;如果正在執(zhí)行的是Natvie方法(一個Native Method就是一個java調(diào)用非java代碼的接口。一個Native Method是這樣一個java的方法:該方法的實現(xiàn)由非java語言實現(xiàn),比如C。),這個計數(shù)器值則為空(Undefined)。
此內(nèi)存區(qū)域是唯一一個在Java虛擬機規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。
- JVM棧
與程序計數(shù)器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息。每一個方法被調(diào)用直至執(zhí)行完成的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。
局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它不等同于對象本身,根據(jù)不同的虛擬機實現(xiàn),它可能是一個指向?qū)ο笃鹗嫉刂返囊弥羔?,也可能指向一個代表對象的句柄或者其他與此對象相關的位置)和returnAddress類型(指向了一條字節(jié)碼指令的地址)。
其中64位長度的long和double類型的數(shù)據(jù)會占用2個局部變量空間(Slot),其余的數(shù)據(jù)類型只占用1個。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。
在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常狀況:如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常;如果虛擬機??梢詣討B(tài)擴展(當前大部分的Java虛擬機都可動態(tài)擴展,只不過Java虛擬機規(guī)范中也允許固定長度的虛擬機棧),當擴展時無法申請到足夠的內(nèi)存時會拋出OutOfMemoryError異常。
- 本地方法棧
本地方法棧(Native Method Stacks)與虛擬機棧所發(fā)揮的作用是非常相似的,其區(qū)別不過是虛擬機棧為虛擬機執(zhí)行Java方法(也就是字節(jié)碼)服務,而本地方法棧則是為虛擬機使用到的Native方法服務。虛擬機規(guī)范中對本地方法棧中的方法使用的語言、使用方式與數(shù)據(jù)結構并沒有強制規(guī)定,因此具體的虛擬機可以自由實現(xiàn)它。甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二為一。與虛擬機棧一樣,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常。
現(xiàn)在用一張圖來介紹每個區(qū)域存儲的內(nèi)容。

③jvm體系之-GC算法 垃圾回收
程序計數(shù)器、虛擬機棧、本地方法棧 3 個區(qū)域隨線程生滅(因為是線程私有),棧中的棧幀隨著方法的進入和退出而有條不紊地執(zhí)行著出棧和入棧操作。而 Java 堆和方法區(qū)則不一樣,一個接口中的多個實現(xiàn)類需要的內(nèi)存可能不一樣,一個方法中的多個分支需要的內(nèi)存也可能不一樣,我們只有在程序處于運行期才知道那些對象會創(chuàng)建,這部分內(nèi)存的分配和回收都是動態(tài)的,垃圾回收器所關注的就是這部分內(nèi)存。
在進行內(nèi)存回收之前要做的事情就是判斷那些對象是‘死’的,哪些是‘活’的。
判斷對象是否存活一般有兩種方式:
1.引用計數(shù):每個對象有一個引用計數(shù)屬性,新增一個引用時計數(shù)加1,引用釋放時計數(shù)減1,計數(shù)為0時可以回收。此方法簡單,無法解決對象相互循環(huán)引用的問題。
2.可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的,不可達對象。
GC算法GC最基礎的算法有三種:標記 -清除算法、復制算法、標記-壓縮算法,我們常用的垃圾回收器一般都采用分代收集算法。
1.標記 -清除算法,“標記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分為“標記”和“清除”兩個階段:
首先標記出所有需要回收的對象,在標記完成后統(tǒng)一回收掉所有被標記的對象。
2.復制算法,“復制”(Copying)的收集算法,它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。
當這一塊的內(nèi)存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。
3.標記-壓縮算法,標記過程仍然與“標記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象進行清理,
而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存
4.分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分為新生代和老年代,
這樣就可以根據(jù)各個年代的特點采用最適當?shù)氖占惴ā?
- 垃圾回收器
1.Serial收集器
串行收集器是最古老,最穩(wěn)定以及效率高的收集器,可能會產(chǎn)生較長的停頓,只使用一個線程去回收。
2.ParNew收集器
ParNew收集器其實就是Serial收集器的多線程版本。
Parallel收集器,Parallel Scavenge收集器類似ParNew收集器,Parallel收集器更關注系統(tǒng)的吞吐量。
3.Parallel Old 收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法
4.CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。
5.G1收集器
G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內(nèi)存的機器.
以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量性能特征
詳細學習記錄:http://www.itdecent.cn/p/8e55c79be2aa
④jvm體系之-GC分析 命令調(diào)優(yōu)
- GC日志分析
http://www.itdecent.cn/p/0ed902739aa0
-
GC日志分析命令
Sun JDK監(jiān)控和故障處理命令有jps jstat jmap jhat jstack jinfo
jps: JVM Process Status Tool,顯示指定系統(tǒng)內(nèi)所有的HotSpot虛擬機進程。
jstat: JVM statistics Monitoring是用于監(jiān)視虛擬機運行時狀態(tài)信息的命令,它可以顯示出虛擬機進程中的類裝載、內(nèi)存、垃圾收集、JIT編譯等運行數(shù)據(jù)。
jmap: JVM Memory Map命令用于生成heap dump文件
jhat: JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump
jhat內(nèi)置了一個微型的HTTP/HTML服務器,生成dump的分析結果后,可以在瀏覽器中查看
jstack: 用于生成java虛擬機當前時刻的線程快照。
jinfo: JVM Configuration info 這個命令作用是實時查看和調(diào)整虛擬機運行參數(shù)。
常用調(diào)優(yōu)工具分為兩類
jdk自帶監(jiān)控工具:jconsole和jvisualvm
第三方有:MAT(Memory Analyzer Tool)、GC Easy。
