1.GC算法html
根搜索算法、標記-清除算法、複製算法、標記-整理算法java
根搜索算法:設立若干種根對象,當任何一個根對象到某一個對象均不可達時,則認爲這個對象是能夠被回收的。程序員
能夠當作GC roots的對象有如下幾種:算法
一、虛擬機棧中的引用的對象。數據庫
二、方法區中的類靜態屬性引用的對象。apache
三、方法區中的常量引用的對象。編程
四、本地方法棧中JNI的引用的對象。bootstrap
標記-清除算法:當堆中的有效內存空間(available memory)被耗盡的時候,就會中止整個程序(也被成爲stop the world),而後進行兩項工做,第一項則是標記,第二項則是清除。緩存
(1)標記:標記的過程其實就是,遍歷全部的GC Roots,而後將全部GC Roots可達的對象標記爲存活的對象。安全
(2)清除:清除的過程將遍歷堆中全部的對象,將沒有標記的對象所有清除掉。
複製算法:複製算法將內存劃分爲兩個區間,在任意時間點,全部動態分配的對象都只能分配在其中一個區間(稱爲活動區間),而另一個區間(稱爲空閒區間)則是空閒的,當有效內存空間耗盡時,JVM將暫停程序運行,開啓複製算法GC線程。接下來GC線程會將活動區間內的存活對象,所有複製到空閒區間,且嚴格按照內存地址依次排列,與此同時,GC線程將更新存活對象的內存引用地址指向新的內存地址。此時,空閒區間已經與活動區間交換,而垃圾對象如今已經所有留在了原來的活動區間,也就是如今的空閒區間。事實上,在活動區間轉換爲空間區間的同時,垃圾對象已經被一次性所有回收。
標記/整理算法:標記/整理算法與標記/清除算法很是類似,它也是分爲兩個階段:標記和整理。
(1)標記:它的第一個階段與標記/清除算法是如出一轍的,均是遍歷GC Roots,而後將存活的對象標記。
(2)整理:移動全部存活的對象,且按照內存地址次序依次排列,而後將末端內存地址之後的內存所有回收。所以,第二階段才稱爲整理階段。
標記/整理算法不只能夠彌補標記/清除算法當中,內存區域分散的缺點,也消除了複製算法當中,內存減半的高額代價,標記/整理算法惟一的缺點就是效率也不高,不只要標記全部存活對象,還要整理全部存活對象的引用地址。
*2.Java內存模型
全部線程共享的數據區:堆、方法區;
線程隔離的數據區:虛擬機棧、本地方法棧、程序計數器;
1,程序計數器(Program Counter Register):程序計數器是一個比較小的內存區域,用於指示當前線程所執行的字節碼執行到了第幾行,能夠理解爲是當前線程的行號指示器。字節碼解釋器在工做時,會經過改變這個計數器的值來取下一條語句指令。每一個程序計數器只用來記錄一個線程的行號,因此它是線程私有(一個線程就有一個程序計數器)的。
2,虛擬機棧(JVM Stack):一個線程的每一個方法在執行的同時,都會建立一個棧幀(Statck Frame),棧幀中存儲的有局部變量表、操做數棧、動態連接、方法出口等,當方法被調用時,棧幀在JVM棧中入棧,當方法執行完成時,棧幀出棧。每一個線程對應着一個虛擬機棧,所以虛擬機棧也是線程私有的。
3,本地方法棧(Native Method Statck):本地方法棧在做用,運行機制,異常類型等方面都與虛擬機棧相同,惟一的區別是:虛擬機棧是執行Java方法的,而本地方法棧是用來執行native方法的,在不少虛擬機中(如Sun的JDK默認的HotSpot虛擬機),會將本地方法棧與虛擬機棧放在一塊兒使用。本地方法棧也是線程私有的。
4,堆區(Heap):堆區是理解Java GC機制最重要的區域,沒有之一。在JVM所管理的內存中,堆區是最大的一塊,堆區也是Java GC機制所管理的主要內存區域,堆區由全部線程共享,在虛擬機啓動時建立。堆區的存在是爲了存儲對象實例,原則上講,全部的對象都在堆區上分配內存(不過現代技術裏,也不是這麼絕對的,也有棧上直接分配的)。
5,方法區(Method Area):方法區是各個線程共享的區域,用於存儲已經被虛擬機加載的類信息(即加載類時須要加載的信息,包括版本、field、方法、接口等信息)、final常量、靜態變量、編譯器即時編譯的代碼等。
*3.內存分配及GC機制
這裏所說的內存分配,主要指的是在堆上的分配。Java內存分配和回收的機制歸納的說,就是:分代分配,分代回收。對象將根據存活的時間被分爲:年輕代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation,也就是方法區)。
年輕代上的內存分配是這樣的,年輕代能夠分爲3個區域:Eden區(伊甸園,亞當和夏娃偷吃禁果生娃娃的地方,用來表示內存首次分配的區域,再貼切不過)和兩個存活區(Survivor 0 、Survivor 1)。
從上面的過程能夠看出,Eden區是連續的空間,且Survivor總有一個爲空。通過一次GC和複製,一個Survivor中保存着當前還活着的對象,而Eden區和另外一個Survivor區的內容都再也不須要了,能夠直接清空,到下一次GC時,兩個Survivor的角色再互換。所以,這種方式分配內存和清理內存的效率都極高,這種垃圾回收的方式就是著名的「中止-複製(Stop-and-copy)」清理法(將Eden區和一個Survivor中仍然存活的對象拷貝到另外一個Survivor中),這不表明着中止複製清理法很高效,其實,它也只在這種狀況下高效,若是在老年代採用中止複製,則挺悲劇的。
老年代存儲的對象比年輕代多得多,並且不乏大對象,對老年代進行內存清理時,若是使用中止-複製算法,則至關低效。通常,老年代用的算法是標記-整理算法,即:標記出仍然存活的對象(存在引用的),將全部存活的對象向一端移動,以保證內存的連續。
永久代的回收有兩種:常量池中的常量,無用的類信息,常量的回收很簡單,沒有引用了就能夠被回收。對於無用的類進行回收,必須保證3點:
4.GC是在何時,對什麼東西,作了什麼事情?
Java與C++之間有一堵由動態內存分配和垃圾收集技術所圍成的高牆,牆外面的人想進去,牆裏面的人想出來。
對於從事C和C++程序開發的開發人員來講,在內存管理領域,他們既是擁有最高權利的皇帝,也是從事最基礎工做的勞動人民-----既擁有每個對象的全部權,又擔負着每個對象從生命開始到終結的維護責任。
對於Java程序員來講,虛擬機的自動內存分配機制的幫助下,再也不須要爲每個new操做去寫配對的delete/free代碼,並且不容易出現內存泄露和內存溢出問題,看起來由虛擬機管理內存一切都很美好。不過,也正是由於Java程序員把內存控制的權利交給Java虛擬機,一旦出現內存泄露和溢出方面的問題,若是不瞭解虛擬機是怎樣使用內存的,那排查錯誤將會是一項異常艱難的工做。
而且好的Java程序在編寫的時候確定要考慮GC的問題,怎樣定義static對象,怎樣new對象效率更高等等問題,簡稱面向GC的編程
也能夠說Java的內存分配管理是一種託管的方式,託管於JVM。
C++通過編譯時直接編譯成機器碼,而Java是編譯成字節碼,由JVM解釋執行。
C++是編譯型語言,而Java兼具編譯型和解釋型語言的特色。
七種垃圾收集器:
Serial(年輕代)、ParNew(年輕代)、Parallel Scavenge(年輕代)、
Serial Old(年老代)、Parallel Old(年老代)、CMS(Concurrent Mark Sweep年老代)、
G1(Garbage Firest)
Serial收集器是最基本、發展歷史最悠久的收集器。這個收集器是一個單線程的收集器,當它工做時必須暫停其餘線程的工做,也就是Stop The World。這顯示是它的缺點, 這也是垃圾收集器一直努力的方向。固然,對於相比其它單線程收集器,Serial收集器簡單而高效。對於桌面應用來講,分配的管理內存不會太多,停頓時間徹底能夠控制在幾十毫秒最多一百毫秒之內。因此,Serial收集器對於運行在Client模式下的虛擬機來講是一個很好的選擇。下圖爲Serial結合Serial Old收集器(後續介紹)的運行過程:
Serial收集器優勢:簡單而高效,對於限定單個CPU的環境來講,Serial收集器因爲沒有線程交互的開銷,專心作垃圾收集天然能夠得到較高的收集效率。Serial收集器依然是Client模式下的默認的新生代垃圾收集器。
ParNew收集器其實就是Serial收集器的多線程版本(多CPU下使用效果較好),下圖爲ParNew結合Serial Old收集器(後續介紹)的運行過程:
ParNew收集器對於Serial來講並無太多的創新之處,但它卻是許多運行在Server模式下的虛擬機中首選的新生代收集器,由於除了Serial收集器外,剩下只有它能與CMS收集器(後續介紹)配合工做了。因此,遺憾的是CMS做爲老年代的收集器,卻沒法與JDK1.4中已經存在的新生代收集器Parallel Scavenge配合工做。
並行(Parallel):指多條垃圾收集線程並行工做,但此時用戶線程仍然處於等待狀態;
併發(Concurrent):指用戶線程與垃圾收集線程同時執行(但不必定是並行的,可能會交替執行),用戶程序在繼續運行,而垃圾收集程序運行於另外一個CPU上;
Parallel Scavenge收集器是一個新生代收集器,它也是使用複製算法的收集器,又是並行的多線程收集器,看上去跟ParNew差很少。可是Parallel Scavenge收集器與其餘收集器不一樣在於:CMS等收集器的關注點在於儘量地縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標則是達到一個可控制的吞吐量(Throughput)。所謂的吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量 = 運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),虛擬機總共運行了100分鐘,其中垃圾收集花費1分鐘,那吞吐量就是99%。
Parallel Scavenge收集器也常常稱爲「吞吐量優先」收集器;
停頓時間越短越適合須要與用戶交互的程序,良好的響應速度能提高用戶體驗,而高吞吐量則能夠高效率地利用CPU時間,儘快完成程序的運算任務,主要適合在後臺運算而不須要太多交互的任務。
Parallel Scavenge收集器參數:
-XX:MaxGCPauseMillis參數控制最大垃圾收集停頓時間
以及直接設置吞吐量大小的-XX:GCTimeRatio參數。
Parallel Scavenge收集器還有一個-XX:UseAdaptiveSizePolicy開關參數,打開參數後就不須要手動指定新生代的大小、Eden與Survivor區的比例、晉升老年代對象年齡等細節參數了,虛擬機會根據當前系統的運行狀況收集性能監控信息,動態調整相關參數。這種調節方式叫自適應的調節策略,也是Parallel Scavenge收集器與ParNew收集器的一個重要區別。
Serial Old是一個老年代收集器,它一樣是一個單線程收集器,使用的是「標記-整理」算法。這個收集器的主要意義也是在於給Client模式下的虛擬機使用。若是在Server模式下,那麼它主要還有兩大用途:一種用途是在JDK1.5以及以前的版本中與Parallel Scavenge收集器搭配使用;另外一種用途就是做爲CMS收集器的後備方案,在併發收集發生ConCurrent Mode Failure時使用。
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和「標記-整理」算法。這個收集器是在JDK1.6中才開始提供的,在此以前,新生代的Parallel Scavenge收集器一直處於比較尷尬的狀態。緣由是新生代若是選擇了Parallel Scavenge收集器,老年代除了Serial Old收集器別無選擇(由於它沒法與CMS配合使用)。在都CPU時代,因爲Serial Old收集器在服務端性能上的「拖累」,使用了Parallel Scavenge收集器也未必能在總體應用上得到吞吐量最大化的效果。直到Parallel Old收集器的出現後,「吞吐量優先」收集器纔有了比較名副其實的應用組合。在注重吞吐量以及CPU資源敏感的場合,均可以優先考慮Parallel Scavenge加Parallel Old收集器,以下圖所示:
CMS(Concurrent Mark Sweep)收集器是一種以得到最短回收停頓時間爲目標的收集器。目前很大一部分的Java應用集中在互聯網站或者B/S系統的服務端上,這類應用尤爲重視服務的響應速度,但願系統停頓時間最短,以給用戶帶來較好的體驗。從名字上就能夠看出,CMS收集器是基於「標記-清除」算法實現的。但它的實際運做過程對於前面幾種收集器來講更復雜一些,整個過程分爲4個步驟:
其中,初始標記、從新標記這兩個步驟仍然須要「Stop The World」。初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快;併發標記階段就是進行GC Roots Tracing的過程;而從新標記階段則是爲了修正併發標記期間用用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄。這個階段的停頓時間通常會比初始標記階段稍長一些,但遠比並發標記時間短。因爲整個過程當中耗時最長的併發標記和併發清除過程收集器線程均可以與用戶線程一塊兒工做,因此,從整體上來講,CMS收集器的內存回收過程是與用戶線程一塊兒併發執行的(注意併發與並行的概念),以下圖所示:
CMS是一款優秀的收集器,可是還遠達不到的完美程度,它有如下3個明顯缺點:
G1(Garbage First)收集器是當今收集器技術發展的最前沿成果之一,從JDK 6u14中開始就有Early Acsess版本的G1收集器供開發人員實驗、試用,由此開始G1收集器的 「Experimental」 狀態持續了數年時間,直到JDK7u4,Sun公司才認爲它達到足夠成熟的商用程度,移除了「Experimental」的標識。G1是一款面向服務端應用的垃圾收集器。HotSpot開發團隊賦予它的使命是將來能夠替換掉JDK1.5中發佈的CMS收集器。其與其它收集器相比,G1具有以下特色:
與其它收集器相比,G1變化較大的是它將整個Java堆劃分爲多個大小相等的獨立區域(Region),雖然還保留了新生代和來年代的概念,但新生代和老年代再也不是物理隔離的了它們都是一部分Region(不須要連續)的集合。同時,爲了不全堆掃描,G1使用了Remembered Set來管理相關的對象引用信息。當進行內存回收時,在GC根節點的枚舉範圍中加入Remembered Set便可保證不對全堆掃描也不會有遺漏了。
若是不計算維護Remembered Set的操做,G1收集器的運做大體可劃分爲如下幾個步驟:
看上去跟CMS收集器的運做過程有幾分類似。初始階段僅僅只是標記一下GC Roots能直接關聯到的對象,而且修改TAMS(Next Top Mark Start)的值,讓下一階段用戶程序併發運行時,能在正確能夠用的Region中建立新對象,這個階段須要停頓線程,但耗時很短。併發標記階段是從GC Roots開始對堆中對象進行可達性分析,找出存活對象,這一階段耗時較長但能與用戶線程併發運行。而最終標記階段須要吧Remembered Set Logs的數據合併到Remembered Set中,這階段須要停頓線程,但可並行執行。最後篩選回收階段首先對各個Region的回收價值和成本進行排序,根據用戶所指望的GC停頓時間來制定回收計劃,這一過程一樣是須要停頓線程的,但Sun公司透露這個階段其實也能夠作到併發,但考慮到停頓線程將大幅度提升收集效率,因此選擇停頓。下圖爲G1收集器運行示意圖:
一、Serial收集器
新生代單線程的收集器,採用複製算法,在進行收集垃圾時,必須stop the world,
它是虛擬機運行在Client模式下的默認新生代收集器;能夠和Serial Old、CMS組合使用;
二、ParNew收集器
是Serial收集器的多線程版本,採用複製算法,回收時須要stop-the-world;
許多運行在Server模式下的虛擬機中首選的新生代收集器;能夠和Serial Old、CMS組合使用;
除Serial外,只有它能與CMS收集器配合工做;
三、Parallel Scavenge收集器
新生代收集器,使用複製算法又是並行的多線程收集器,關注系統吞吐量;
參數:
-XX:MaxGCPauseMillis:最大垃圾收集停頓時間;
-XX:GCTimeRatio:吞吐量大小;
-XX:UseAdaptiveSizePolicy:自適應的調節策略;打開參數後就不須要手動指定新生代的大小、Eden與Survivor區的比例、晉升老年代對象年齡等細節參數了,虛擬機會根據當前系統的運行狀況收集性能監控信息,動態調整相關參數;
四、Serial Old收集器
是Serial收集器的老年代版本,一樣是單線程收集器,使用標記整理算法。
五、Parallel Old收集器
是Parallel Scavenge收集器的老年代版本,使用多線程和標記整理算法。
六、CMS收集器(Concurrent Mark Sweep)
老年代收集器;是一種以得到最短回收停頓時間爲目標的收集器,基於標記清除算法。
過程以下:初始標記,併發標記,從新標記,併發清除;其中初始標記和從新標記過程須要stop-the-world;
優勢是併發收集,低停頓,缺點是對CPU資源很是敏感,沒法處理浮動垃圾,收集結束會產生大量空間碎片。
參數:
UserCMSCompactAtFullCollection:默認開啓,FullGC時進行內存碎片整理,整理時用戶進程需中止,即發生Stop The World;
CMSFullGCsBeforeCompaction:設置執行多少次不壓縮的Full GC後,執行一個帶壓縮的(默認爲0,表示每次進入Full GC時都進行碎片整理);
七、G1收集器
是基於標記整理算法實現的,不會產生空間碎片,
過程:初始標記、併發標記、最終標記、篩選回收
優勢:並行與併發、分代收集、空間整合、可預測的停頓;
能夠精確地控制停頓,將堆劃分爲多個大小固定的獨立區域,並跟蹤這些區域的垃圾堆積程度,在後臺維護一個優先列表,每次根據容許的收集時間,優先回收垃圾最多的區域(Garbage First)。
*1.ClassLoader類加載器
*2.雙親委派模型
雙親委派模型的工做流程是:若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把請求委託給父加載器去完成,依次向上,所以,全部的類加載請求最終都應該被傳遞到頂層的啓動類加載器中,只有當父加載器在它的搜索範圍中沒有找到所需的類時,即沒法完成該加載,子加載器纔會嘗試本身去加載該類。
使用雙親委派模型來組織類加載器之間的關係,有一個很明顯的好處,就是Java類隨着它的類加載器(說白了,就是它所在的目錄)一塊兒具有了一種帶有優先級的層次關係,這對於保證Java程序的穩定運做很重要。例如,類java.lang.Object類存放在JDK\jre\lib下的rt.jar之中,所以不管是哪一個類加載器要加載此類,最終都會委派給啓動類加載器進行加載,這便保證了Object類在程序中的各類類加載器中都是同一個類。
3.類加載簡單過程
6.類加載器結構
Java 中的類加載器大體能夠分紅兩類:
一類是系統提供的:
另一類則是由 Java 應用開發人員編寫的:
從1.2版本開始,Java引入了雙親委託模型,從而更好的保證Java平臺的安全。在此模型下,當一個裝載器被請求裝載某個類時,它首先委託本身的parent去裝載,若parent能裝載,則返回這個類所對應的Class對象,若parent不能裝載,則由parent的請求者去裝載。(爲何更加安全?由於在此模型下用戶自定義的類裝載器不可能裝載應該由父親裝載器裝載的可靠類,從而防止不可靠甚至惡意的代碼代替由父親裝載器裝載的可靠代碼。)
具體示例:
假如loader2的parent爲loader1,loader1的parent爲system class loader。假設loader2被要求裝載類MyClass,在parent delegation模型下,loader2首先請求loader1代爲裝載,loader1再請求系統類裝載器去裝載MyClass。若系統裝載器能成功裝載,則將MyClass所對應的Class對象的reference返回給loader1,loader1再將reference返回給loader2,從而成功將類MyClass裝載進虛擬機。若系統類裝載器不能裝載MyClass,loader1會嘗試裝載MyClass,若loader1也不能成功裝載,loader2會嘗試裝載。若全部的parent及loader2自己都不能裝載,則裝載失敗。
參考:ClassLoader
*8.類加載過程
類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的整個生命週期包括:加載、驗證、準備、解析、初始化、使用和卸載七個階段。它們開始的順序以下圖所示:
其中類加載的過程包括了加載、驗證、準備、解析、初始化五個階段。在這五個階段中,加載、驗證、準備和初始化這四個階段發生的順序是肯定的,而解析階段則不必定,它在某些狀況下能夠在初始化階段以後開始,這是爲了支持Java語言的運行時綁定(也成爲動態綁定或晚期綁定)。另外注意這裏的幾個階段是按順序開始,而不是按順序進行或完成,由於這些階段一般都是互相交叉地混合進行的,一般在一個階段執行的過程當中調用或激活另外一個階段。
加載
加載時類加載過程的第一個階段,在加載階段,虛擬機須要完成如下三件事情:
(1.字節流;2.方法區數據結構;3.訪問入口)
一、經過一個類的全限定名來獲取定義此類的二進制字節流。
二、將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。
三、在內存中生成一個表明這個類的java.lang.Class對象,做爲對方法區中這些數據的訪問入口。
驗證
驗證的目的是爲了確保Class文件中的字節流包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。不一樣的虛擬機對類驗證的實現可能會有所不一樣,但大體都會完成如下四個階段的驗證:文件格式的驗證、元數據的驗證、字節碼驗證和符號引用驗證。
準備
準備階段是正式爲類變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中分配。對於該階段有如下幾點須要注意:
一、這時候進行內存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨着對象一塊分配在Java堆中。
二、這裏所設置的初始值一般狀況下是數據類型默認的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。
解析