在上一篇深刻理解jvm虛擬機一中說了Jvm幾種垃圾回收算法,針對年代不一樣,Jvm爲咱們提供了幾種垃圾收集器。java
Java爲咱們提供了不少垃圾收集器,Young爲新生代所選擇的,Tenured爲老年代所選,能夠相互配合使用,沒有最好的收集器,只有最適合的。如圖所示: 算法
Serial是最基本的垃圾回收器,它是一個單線程的收集器,不但只會使用一個CPU或一條線程去完成垃圾收集,更重要的是它在進行收集時,必須暫停其餘全部的線程,直到垃圾回收完畢,由於不須要線程間切換,能夠得到更高效的執行效率,所以它也是Java虛擬機運行在Client模式下默認新生代垃圾收集器。 數組
ParNew實際上是Serial多線程版本,也是使用複製算法,除了使用多線程進行垃圾收集之外,其餘跟Serial同樣,在進行垃圾收集過程當中也須要中止全部其餘線程操做,默認開啓跟CPU數目相同的線程數(ParallelGCThreads設置數量)。它是多數Java虛擬機運行在Server模式下默認新生代垃圾收集器。 安全
Parallel也是新生代的垃圾回收器,一樣才用複製算法,可是它是重點關注一個可控制的吞吐量(CPU 用於運行用戶代碼 的時間/CPU 總消耗時間,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)),能夠有效提升CPU使用率,儘快的完成運算,偏向於後臺計算而不須要太多交互,自適應調節策略也是 Parallel 與 ParNew 的一個重要區別。多線程
Serial Old是Serial垃圾收集器的老年代版本,一樣是一個單線程的收集器,使用標記整理算法,主要是運行在Client模式下老年代的垃圾收集器,若是在Server模式下,還有兩個功能:在 JDK1.5 以前版本中與新生代的 Parallel Scavenge 收集器搭配使用;做爲年老代CMS備用垃圾收集器,在併發收集發生「Concurrent Mode Failure」時使用。 併發
Parallel Old是Parallel老年代垃圾回收器,使用多線程標記整理算法,在Jdk1.6之前Parallel只能配合Serial Old使用,沒法保證總體的吞吐量,Parallel Old 是爲了提升老年代吞吐量而提供的。 jvm
Concuuent mark sweep是一種年老代垃圾收集器,主要目標是獲取最短垃圾回收停頓時間,和其餘老年代標記整理不同,它使用的多線程標記清除,最短垃圾回收停頓時間能夠爲頻繁交互的程序提升用戶體驗。整個過程爲如下四步post
1. 初始標記:只是標記roots能直接關聯到的對象,速度很快,須要暫停其餘線程。 2. 併發標記:進行roots跟蹤的過程,和用戶線程一塊兒工做,不須要暫停其餘線程。 3. 從新標記:爲了修正在併發標記時,用戶操做產生變更的一部分對象引用標記記錄,須要暫停其餘線程。 4. 併發清除:清除不可達對象,和用戶線程一塊兒工做,不須要暫停其餘線程。 因爲是使用是標記清除,可能出現「Concurrent Mode Failure」失敗而致使一次Full GC,因爲CMS在併發清除的時候還會有垃圾進來,若是CMS預留空間沒法知足程序須要,虛擬機就會啓動備用方案:臨時啓用Serial old收集器回收老年代資源,這樣停頓時間就會更長,所以還須要留有空間來存儲垃圾,不能等到老年代被填滿了在進行GC,設置CMSInitiatingOccupancyFraction(默認是92),只有達到這個峯值纔會進行垃圾回收,配合UseCMSInitiatingOccupancyOnly一塊兒使用,只有CMSInitiatingOccupancyFraction設置後纔會生效,若是不指定CMSInitiatingOccupancyFraction,JVM僅在第一次使用設定值,後續則自動調整。 CMS沒法浮動垃圾會產生碎片,若是碎片不少,沒法放入大對象,不得不提早觸發一次Full GC,爲了防止這種狀況出現,CMS提供了UseCMSCompactAtFullConllection開關參數(默認開啓),用於頂不住要Full GC的時候來進行內存碎片整合。因爲碎片整合是沒法併發的,空間問題解決了可是停頓時間就長了,還須要設置CMSFullGCBeforeCompaction(默認0次,表示每次次Full GC的時候都會壓縮),這個參數設置多少次不壓縮Full GC後,而後來一次帶壓縮的。 線程
Garbage first(UseG1GC )收集器是當今收集器技術發展的最前沿成果之一,它是一款面向服務端應用的垃圾回收器,相對比CMS,G1是兩個優勢:1.基於標記-整理,不會產生碎片。2.能夠很是精確控制停頓時間(XX:MaxGCPauseMillis),在不犧牲吞吐量的狀況下,實現低停頓垃圾回收。它將整個堆分爲若干個大小相等的獨立區域(Region)(Eden區 Survivor區 Old區 Humongous區)Humongous爲放至巨型對象,雖然保留新生代和老年代概念,可是再也不是物理隔離,它們都是一部分Region(不須要連續)的集合。 3d
類從加載到虛擬機內存中開始,到從內存中卸載,它的整個生命週期包括:加載、鏈接(驗證、準備、解析)、初始化、使用、卸載。 加載、驗證、準備、初始化和卸載這五個階段的順序是肯定的,類加載必須按照這種順序來加載,而解析就不必定了,它在某些狀況下能夠在初始化後再開始,符合Java語言的運行時綁定。
會在內存中生成一個這個類的Class對象,做爲方法區這個類的各類數據入口。這裏的並非從Class文件中獲取,既能夠從ZIP包中讀取(jar、war),也能夠在運行時動態生成,也能夠從其餘文件生成(JSP轉爲Class對象)。
是爲了確保Class文件的字節流包含的信息是否符合當前虛擬機的要求,而且不會危害虛擬機自身。分爲四個階段完成: 1. 文件格式驗證:驗證字節流是否符合Class文件格式規範。 2. 元數據驗證:驗證字節流的信息是否符合Java語言規範要求。 3. 字節碼驗證:肯定程序語義是合法的、符合邏輯的。 4. 符合引用驗證:對常量池中符號引用的信息進行匹配性校驗。
是爲類變量分配內存並初始值的階段。 類變量即被static修飾的變量,不包括實例變量。注意這裏所說的初始值概念,好比一個類變量定義爲:public static int a = 8080;實際上變量 a 在準備階段事後的初始值爲 0 而不是 8080,將 v 賦值爲 8080 的 put static 指令是程序被編譯後,存放於類構造器client方法之中。可是注意若是聲明爲:public static final int a = 8080;在編譯階段會爲 a 生成 ConstantValue 屬性,在準備階段虛擬機會根據 ConstantValue 屬性將 a 賦值爲 8080。
虛擬機將常量池中的符合引用替換爲直接引用。符號引用能夠當作一個標識,好比一個方法、接口等,直接引用則是指向內存中的地址。
類加載最後一個階段,除了加載能夠自定義累加器以外,其餘都是由JVM自動完成,到了初始化階段,纔開始真正的執行類中Java代碼。特別注意如下狀況並不會觸發初始化: 1. 經過子類引用父類的靜態字段,只會觸發父類的初始化,而不會觸發子類的初始化。 2. 定義對象數組,不會觸發該類的初始化。 3. 常量在編譯期間會存入調用類的常量池中,本質上並無直接引用定義常量的類,不會觸發定義常量所在的類。 4. 經過類名獲取 Class 對象,不會觸發類的初始化。 5. 經過 Class.forName 加載指定類時,若是指定參數 initialize 爲 false 時,也不會觸發類初始化,其實這個參數是告訴虛擬機,是否要對類進行初始化。 6. 經過 ClassLoader 默認的 loadClass 方法,也不會觸發初始化動做。
第一種狀況:
輸出結果: I am father 1
第二種狀況:
無任何輸出
第三種狀況
輸出結果:1
對於一個類來講,都須要由加載它的類加載器和這個類自己一同確立在Java虛擬機中的惟一性。在比較兩個類是否相等,只有在同一個類加載器加載的狀況下才有意義。JVM提供了三種類加載器:
負責加載JAVA_HOME\lib目錄中的或經過設置-XbootClassPath參數指定的路徑中的,而且是虛擬機識別(按文件名識別)的類庫加載到虛擬機內存中。
負責加載JAVA_HOME\lib\ext目錄中的或者經過 java.ext.dirs系統變量指定路徑中的類庫。
負責加載用戶路徑(ClassPath)下的類庫。 JVM經過雙親委派模型進行類加載,固然咱們也能夠繼承ClassLoader實現自定義類加載器。
當一個類收到了類加載請求後,首先本身不會嘗試加載,而是把這個請求交給父類加載,每一層類加載器都是如此,只有當父類沒法加載子類纔會嘗試本身加載,不論是哪一個加載器加載這個類,最終都委派給頂層的啓動類加載器,這樣就保證了使用不一樣類加載器獲得的都是同一個對象,保證惟一性和安全性。