1:什麼是JVMjava
JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用於計算設備的規範,它是一個虛構出來的計算機,是經過在實際的計算機上仿真模擬各類計算機功能來實現的。Java虛擬機包括一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。 JVM屏蔽了與具體操做系統平臺相關的信息,使Java程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就能夠在多種平臺上不加修改地運行。JVM在執行字節碼時,實際上最終仍是把字節碼解釋成具體平臺上的機器指令執行。算法
2:JRE/JDK/JVM是什麼關係數組
JRE(JavaRuntimeEnvironment,Java運行環境),也就是Java平臺。全部的Java 程序都要在JRE下才能運行。普通用戶只須要運行已開發好的java程序,安裝JRE便可。緩存
JDK(Java Development Kit)是程序開發者用來來編譯、調試java程序用的開發工具包。JDK的工具也是Java程序,也須要JRE才能運行。爲了保持JDK的獨立性和完整性,在JDK的安裝過程當中,JRE也是 安裝的一部分。因此,在JDK的安裝目錄下有一個名爲jre的目錄,用於存放JRE文件。多線程
JVM(JavaVirtualMachine,Java虛擬機)是JRE的一部分。它是一個虛構出來的計算機,是經過在實際的計算機上仿真模擬各類計算機功能來實現的。JVM有本身完善的硬件架構,如處理器、堆棧、寄存器等,還具備相應的指令系統。Java語言最重要的特色就是跨平臺運行。使用JVM就是爲了支持與操做系統無關,實現跨平臺。架構
3:JVM原理併發
JVM是java的核心和基礎,在java編譯器和os平臺之間的虛擬處理器。它是一種利用軟件方法實現的抽象的計算機基於下層的操做系統和硬件平臺,能夠在上面執行java的字節碼程序。工具
java編譯器只要面向JVM,生成JVM能理解的代碼或字節碼文件。Java源文件經編譯成字節碼程序,經過JVM將每一條指令翻譯成不一樣平臺機器碼,經過特定平臺運行。性能
4:JVM的體系結構開發工具
類裝載器(ClassLoader)(用來裝載.class文件)
執行引擎(執行字節碼,或者執行本地方法)
運行時數據區(方法區、堆、java棧、PC寄存器、本地方法棧)
5:JVM運行時數據區
第一塊:PC寄存器
PC寄存器是用於存儲每一個線程下一步將執行的JVM指令,如該方法爲native的,則PC寄存器中不存儲任何信息。
第二塊:JVM棧
JVM棧是線程私有的,每一個線程建立的同時都會建立JVM棧,JVM棧中存放的爲當前線程中局部基本類型的變量(java中定義的八種基本類型:boolean、char、byte、short、int、long、float、double)、部分的返回結果以及Stack Frame,非基本類型的對象在JVM棧上僅存放一個指向堆上的地址。
第三塊:堆(Heap)
它是JVM用來存儲對象實例以及數組值的區域,能夠認爲Java中全部經過new建立的對象的內存都在此分配,Heap中的對象的內存須要等待GC進行回收。
(1) 堆是JVM中全部線程共享的,所以在其上進行對象內存的分配均須要進行加鎖,這也致使了new對象的開銷是比較大的
(2) Sun Hotspot JVM爲了提高對象內存分配的效率,對於所建立的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的狀況計算而得,在TLAB上分配對象時不須要加鎖,所以JVM在給線程的對象分配內存時會盡可能的在TLAB上分配,在這種狀況下JVM中分配對象內存的性能和C基本是同樣高效的,但若是對象過大的話則仍然是直接使用堆空間分配
(3) TLAB僅做用於新生代的Eden Space,所以在編寫Java程序時,一般多個小的對象比大的對象分配起來更加高效。
(4) 全部新建立的Object 都將會存儲在新生代Yong Generation中。若是Young Generation的數據在一次或屢次GC後存活下來,那麼將被轉移到OldGeneration。新的Object老是建立在Eden Space。
第四塊:方法區域(Method Area)
(1)在Sun JDK中這塊區域對應的爲PermanetGeneration,又稱爲持久代。
(2)方法區域存放了所加載的類的信息(名稱、修飾符等)、類中的靜態變量、類中定義爲final類型的常量、類中的Field信息、類中的方法信息,當開發人員在程序中經過Class對象中的getName、isInterface等方法來獲取信息時,這些數據都來源於方法區域,同時方法區域也是全局共享的,在必定的條件下它也會被GC,當方法區域須要使用的內存超過其容許的大小時,會拋出OutOfMemory的錯誤信息。
第五塊:運行時常量池(Runtime Constant Pool)
存放的爲類中的固定的常量信息、方法和Field的引用信息等,其空間從方法區域中分配。
第六塊:本地方法堆棧(Native Method Stacks)
JVM採用本地方法堆棧來支持native方法的執行,此區域用於存儲每一個native方法調用的狀態。
6:對象「已死」的斷定算法
因爲程序計數器、Java虛擬機棧、本地方法棧都是線程獨享,其佔用的內存也是隨線程生而生、隨線程結束而回收。而Java堆和方法區則不一樣,線程共享,是GC的所關注的部分。
在堆中幾乎存在着全部對象,GC以前須要考慮哪些對象還活着不能回收,哪些對象已經死去能夠回收。
有兩種算法能夠斷定對象是否存活:
1.)引用計數算法:給對象中添加一個引用計數器,每當一個地方應用了對象,計數器加1;當引用失效,計數器減1;當計數器爲0表示該對象已死、可回收。可是它很難解決兩個對象之間相互循環引用的狀況。
2.)可達性分析算法:經過一系列稱爲「GC Roots」的對象做爲起點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(即對象到GC Roots不可達),則證實此對象已死、可回收。Java中能夠做爲GC Roots的對象包括:虛擬機棧中引用的對象、本地方法棧中Native方法引用的對象、方法區靜態屬性引用的對象、方法區常量引用的對象。
在主流的商用程序語言(如咱們的Java)的主流實現中,都是經過可達性分析算法來斷定對象是否存活的。
7:JVM垃圾回收
GC (Garbage Collection)的基本原理:將內存中再也不被使用的對象進行回收,GC中用於回收的方法稱爲收集器,因爲GC須要消耗一些資源和時間,Java在對對象的生命週期特徵進行分析後,按照新生代、舊生代的方式來對對象進行收集,以儘量的縮短GC對應用形成的暫停
(1)對新生代的對象的收集稱爲minor GC;
(2)對舊生代的對象的收集稱爲Full GC;
(3)程序中主動調用System.gc()強制執行的GC爲Full GC。
不一樣的對象引用類型, GC會採用不一樣的方法進行回收,JVM對象的引用分爲了四種類型:
(1)強引用:默認狀況下,對象採用的均爲強引用(這個對象的實例沒有其餘對象引用,GC時纔會被回收)
(2)軟引用:軟引用是Java中提供的一種比較適合於緩存場景的應用(只有在內存不夠用的狀況下才會被GC)
(3)弱引用:在GC時必定會被GC回收
(4)虛引用:因爲虛引用只是用來得知對象是否被GC
8:垃圾收集算法
一、標記-清除算法
最基礎的算法,分標記和清除兩個階段:首先標記處所須要回收的對象,在標記完成後統一回收全部被標記的對象。
它有兩點不足:一個效率問題,標記和清除過程都效率不高;一個是空間問題,標記清除以後會產生大量不連續的內存碎片(相似於咱們電腦的磁盤碎片),空間碎片太多致使須要分配大對象時沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾回收動做。
二、複製算法
爲了解決效率問題,出現了「複製」算法,他將可用內存按容量劃分爲大小相等的兩塊,每次只須要使用其中一塊。當一塊內存用完了,將還存活的對象複製到另外一塊上面,而後再把剛剛用完的內存空間一次清理掉。這樣就解決了內存碎片問題,可是代價就是能夠用內容就縮小爲原來的一半。
三、標記-整理算法
複製算法在對象存活率較高時就會進行頻繁的複製操做,效率將下降。所以又有了標記-整理算法,標記過程同標記-清除算法,可是在後續步驟不是直接對對象進行清理,而是讓全部存活的對象都向一側移動,而後直接清理掉端邊界之外的內存。
四、分代收集算法
當前商業虛擬機的GC都是採用分代收集算法,這種算法並無什麼新的思想,而是根據對象存活週期的不一樣將堆分爲:新生代和老年代,方法區稱爲永久代(在新的版本中已經將永久代廢棄,引入了元空間的概念,永久代使用的是JVM內存而元空間直接使用物理內存)。
這樣就能夠根據各個年代的特色採用不一樣的收集算法。
新生代中的對象「朝生夕死」,每次GC時都會有大量對象死去,少許存活,使用複製算法。新生代又分爲Eden區和Survivor區(Survivor from、Survivor to),大小比例默認爲8:1:1。
老年代中的對象由於對象存活率高、沒有額外空間進行分配擔保,就使用標記-清除或標記-整理算法。
新產生的對象優先進去Eden區,當Eden區滿了以後再使用Survivor from,當Survivor from 也滿了以後就進行Minor GC(新生代GC),將Eden和Survivor from中存活的對象copy進入Survivor to,而後清空Eden和Survivor from,這個時候原來的Survivor from成了新的Survivor to,原來的Survivor to成了新的Survivor from。複製的時候,若是Survivor to 沒法容納所有存活的對象,則根據老年代的分配擔保(相似於銀行的貸款擔保)將對象copy進去老年代,若是老年代也沒法容納,則進行Full GC(老年代GC)。
大對象直接進入老年代:JVM中有個參數配置-XX:PretenureSizeThreshold,令大於這個設置值的對象直接進入老年代,目的是爲了不在Eden和Survivor區之間發生大量的內存複製。
長期存活的對象進入老年代:JVM給每一個對象定義一個對象年齡計數器,若是對象在Eden出生並通過第一次Minor GC後仍然存活,而且能被Survivor容納,將被移入Survivor而且年齡設定爲1。沒熬過一次Minor GC,年齡就加1,當他的年齡到必定程度(默認爲15歲,能夠經過XX:MaxTenuringThreshold來設定),就會移入老年代。可是JVM並非永遠要求年齡必須達到最大年齡纔會晉升老年代,若是Survivor 空間中相同年齡(如年齡爲x)全部對象大小的總和大於Survivor的一半,年齡大於等於x的全部對象直接進入老年代,無需等到最大年齡要求。
9:垃圾收集器
垃圾收集算法是方法論,垃圾收集器是具體實現。JVM規範對於垃圾收集器的應該如何實現沒有任何規定,所以不一樣的廠商、不一樣版本的虛擬機所提供的垃圾收集器差異較大,這裏只看HotSpot虛擬機。
JDK7/8後,HotSpot虛擬機全部收集器及組合(連線)以下:
1.Serial收集器
Serial收集器是最基本、歷史最久的收集器,曾是新生代手機的惟一選擇。他是單線程的,只會使用一個CPU或一條收集線程去完成垃圾收集工做,而且它在收集的時候,必須暫停其餘全部的工做線程,直到它結束,即「Stop the World」。停掉全部的用戶線程,對不少應用來講難以接受。好比你在作一件事情,被別人強制停掉,你內心奔騰而過的「羊駝」還數的過來嗎?
儘管如此,它仍然是虛擬機運行在client模式下的默認新生代收集器:簡單而高效(與其餘收集器的單個線程相比,由於沒有線程切換的開銷等)。
工做示意圖:
2.ParNew收集器
ParNew收集器是Serial收集器的多線程版本,除了使用了多線程以外,其餘的行爲(收集算法、stop the world、對象分配規則、回收策略等)同Serial收集器同樣。
是許多運行在Server模式下的JVM中首選的新生代收集器,其中一個很重還要的緣由就是除了Serial以外,只有他能和老年代的CMS收集器配合工做。
工做示意圖:
3.Parallel Scavenge收集器
新生代收集器,並行的多線程收集器。它的目標是達到一個可控的吞吐量(就是CPU運行用戶代碼的時間與CPU總消耗時間的比值,即 吞吐量=行用戶代碼的時間/[行用戶代碼的時間+垃圾收集時間]),這樣能夠高效率的利用CPU時間,儘快完成程序的運算任務,適合在後臺運算而不須要太多交互的任務。
4.Serial Old收集器
Serial 收集器的老年代版本,單線程,「標記整理」算法,主要是給Client模式下的虛擬機使用。
另外還能夠在Server模式下:
JDK 1.5以前的版本中雨Parallel Scavenge 收集器搭配使用
能夠做爲CMS的後背方案,在CMS發生Concurrent Mode Failure是使用
工做示意圖:
5.Parallel Old收集器
Parallel Scavenge的老年代版本,多線程,「標記整理」算法,JDK 1.6纔出現。在此以前Parallel Scavenge只能同Serial Old搭配使用,因爲Serial Old的性能較差致使Parallel Scavenge的優點發揮不出來,尷了個尬~~
Parallel Old收集器的出現,使「吞吐量優先」收集器終於有了名副其實的組合。在吞吐量和CPU敏感的場合,均可以使用Parallel Scavenge/Parallel Old組合。組合的工做示意圖以下:
6.CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器,停頓時間短,用戶體驗就好。
基於「標記清除」算法,併發收集、低停頓,運做過程複雜,分4步:
1)初始標記:僅僅標記GC Roots能直接關聯到的對象,速度快,可是須要「Stop The World」
2)併發標記:就是進行追蹤引用鏈的過程,能夠和用戶線程併發執行。
3)從新標記:修正併發標記階段因用戶線程繼續運行而致使標記發生變化的那部分對象的標記記錄,比初始標記時間長但遠比並發標記時間短,須要「Stop The World」
4)併發清除:清除標記爲能夠回收對象,能夠和用戶線程併發執行
因爲整個過程耗時最長的併發標記和併發清除均可以和用戶線程一塊兒工做,因此整體上來看,CMS收集器的內存回收過程和用戶線程是併發執行的。
工做示意圖:
CSM收集器有3個缺點:
1)對CPU資源很是敏感
併發收集雖然不會暫停用戶線程,但由於佔用一部分CPU資源,仍是會致使應用程序變慢,總吞吐量下降。
CMS的默認收集線程數量是=(CPU數量+3)/4;當CPU數量多於4個,收集線程佔用的CPU資源多於25%,對用戶程序影響可能較大;不足4個時,影響更大,可能沒法接受。
2)沒法處理浮動垃圾(在併發清除時,用戶線程新產生的垃圾叫浮動垃圾),可能出現"Concurrent Mode Failure"失敗。
併發清除時須要預留必定的內存空間,不能像其餘收集器在老年代幾乎填滿再進行收集;若是CMS預留內存空間沒法知足程序須要,就會出現一次"Concurrent Mode Failure"失敗;這時JVM啓用後備預案:臨時啓用Serail Old收集器,而致使另外一次Full GC的產生;
3)產生大量內存碎片:CMS基於"標記-清除"算法,清除後不進行壓縮操做產生大量不連續的內存碎片,這樣會致使分配大內存對象時,沒法找到足夠的連續內存,從而須要提早觸發另外一次Full GC動做。
7.G1收集器
G1(Garbage-First)是JDK7-u4才正式推出商用的收集器。G1是面向服務端應用的垃圾收集器。它的使命是將來能夠替換掉CMS收集器。
G1收集器特性:
並行與併發:能充分利用多CPU、多核環境的硬件優點,縮短停頓時間;能和用戶線程併發執行。
分代收集:G1能夠不須要其餘GC收集器的配合就能獨立管理整個堆,採用不一樣的方式處理新生對象和已經存活一段時間的對象。
空間整合:總體上看採用標記整理算法,局部看採用複製算法(兩個Region之間),不會有內存碎片,不會由於大對象找不到足夠的連續空間而提早觸發GC,這點優於CMS收集器。
可預測的停頓:除了追求低停頓還能創建能夠預測的停頓時間模型,能讓使用者明確指定在一個長度爲M毫秒的時間片斷內,消耗在垃圾收集上的時間不超N毫秒,這點優於CMS收集器。
爲何能作到可預測的停頓?
是由於能夠有計劃的避免在整個Java堆中進行全區域的垃圾收集。
G1收集器將內存分大小相等的獨立區域(Region),新生代和老年代概念保留,可是已經再也不物理隔離。
G1跟蹤各個Region得到其收集價值大小,在後臺維護一個優先列表;
每次根據容許的收集時間,優先回收價值最大的Region(名稱Garbage-First的由來);
這就保證了在有限的時間內能夠獲取儘量高的收集效率。
對象被其餘Region的對象引用了怎麼辦?
判斷對象存活時,是否須要掃描整個Java堆才能保證準確?在其餘的分代收集器,也存在這樣的問題(而G1更突出):新生代回收的時候不得不掃描老年代?不管G1仍是其餘分代收集器,JVM都是使用Remembered Set來避免全局掃描:每一個Region都有一個對應的Remembered Set;每次Reference類型數據寫操做時,都會產生一個Write Barrier 暫時中斷操做;而後檢查將要寫入的引用指向的對象是否和該Reference類型數據在不一樣的 Region(其餘收集器:檢查老年代對象是否引用了新生代對象);若是不一樣,經過CardTable把相關引用信息記錄到引用指向對象的所在Region對應的Remembered Set中;進行垃圾收集時,在GC根節點的枚舉範圍加入 Remembered Set ,就能夠保證不進行全局掃描,也不會有遺漏。
不計算維護Remembered Set的操做,回收過程能夠分爲4個步驟(與CMS較爲類似):
1)初始標記:僅僅標記GC Roots能直接關聯到的對象,並修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序併發運行時能在正確可用的Region中建立新對象,須要「Stop The World」
2)併發標記:從GC Roots開始進行可達性分析,找出存活對象,耗時長,可與用戶線程併發執行
3)最終標記:修正併發標記階段因用戶線程繼續運行而致使標記發生變化的那部分對象的標記記錄。併發標記時虛擬機將對象變化記錄在線程Remember Set Logs裏面,最終標記階段將Remember Set Logs整合到Remember Set中,比初始標記時間長但遠比並發標記時間短,須要「Stop The World」
4)篩選回收:首先對各個Region的回收價值和成本進行排序,而後根據用戶指望的GC停頓時間來定製回收計劃,最後按計劃回收一些價值高的Region中垃圾對象。回收時採用複製算法,從一個或多個Region複製存活對象到堆上的另外一個空的Region,而且在此過程當中壓縮和釋放內存;能夠併發進行,下降停頓時間,並增長吞吐量。
工做示意圖:
10:基本結構
從Java平臺的邏輯結構上來看,咱們能夠從下圖來了解JVM:
從上圖能清晰看到Java平臺包含的各個邏輯模塊,也能瞭解到JDK與JRE的區別。