在Java中主要有以下三種類加載器:

引導(dǎo)類加載器(bootstrap class loader)


--用來加載java的核心庫(String,Integer,List......)在jre/lib/rt.jar路徑下的內(nèi)容。使用c代碼來實現(xiàn)的,并不繼承自java.lang.ClassLoader.


--加載擴展類加載器和應(yīng)用程序加載器,并指定他們的父類加載器。


擴展類加載器(extensions class loader)


--用來加載java的擴展庫(jre/ext/*.jar路徑下的內(nèi)容),java虛擬機的實現(xiàn)會自動提供一個擴展目錄。該類加載器在此目錄里面查找并加載java類。


應(yīng)用程序類加載器(application class loader)


--它根據(jù)java應(yīng)用的類路徑(classpath路徑),一般來說java應(yīng)用的類都是由它來完成加載的。


自定義類加載器


--開發(fā)人員可以通過繼承java.lang.ClassLoader類的方式實現(xiàn)在即的類加載器,以滿足一些特殊的要求。


擴展類加載器、應(yīng)用程序類加載器和自定義類加載器都是由java實現(xiàn),都繼承java.lang.ClassLoader類。




類加載器的代理模式:雙親委托機制

當(dāng)某個類加載器在接收到加載類的請求后,首先將加載任務(wù)委托給父類加載器,依次追溯,如果父類加載器能夠完成類加載任務(wù),就成功返回,只有父類加載器無法完成加載任務(wù)是,才自己加載。


雙親機制是為了保證java核心庫的類型安全,不會出現(xiàn)用戶能自定義java.lang.Object類的情況。


雙親委托機制是代理模式的一種,并不是所有類加載器都采用雙親委托機制,Tomcat服務(wù)器類加載器也使用代理模式,不同的是它是首先嘗試自己去加載某個類,如果找不到再代理給父類加載器。




類加載機制

jvm把class文件加載到內(nèi)存,并對數(shù)據(jù)進行校驗、解析和初始化,最終形成jvm可以直接使用的java類型的過程。


類加載過程:類從被加載到虛擬機內(nèi)存中開始,直到卸載出內(nèi)存為止,它的整個生命周期包括7個階段:加載、驗證、準(zhǔn)備、解析、初始化、使用、卸載(其中驗證、準(zhǔn)備和解析這三個部分統(tǒng)稱為連接)。其中加載、驗證、準(zhǔn)備、初始化和卸載這五個階段的順序是一定的,而解析階段不一定,在某種情況下,可以在初始化之后再開始,這是為了支持java語言的運行時綁定。




加載:將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)中的運行時數(shù)據(jù)結(jié)構(gòu),在堆中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)類數(shù)據(jù)的訪問入口。

連接:將java類的二進制代碼合并到j(luò)vm的運行狀態(tài)之中的過程。驗證:確保加載的類信息符合jvm規(guī)范,沒有安全方面的問題。準(zhǔn)備:正式為類變量(static變量)分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中進行。解析:虛擬機常量池內(nèi)的符號引用替換為直接引用的過程。(比如String s = "aaa",轉(zhuǎn)化為s的地址指向"aaa"的地址)。

初始化:初始化階段是執(zhí)行類構(gòu)造器方法的過程,類構(gòu)造器方法是由編譯器自動收集類中的所有變量的賦值動作和靜態(tài)語句塊(static塊)中的語句合并產(chǎn)生的。當(dāng)初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進行初始化,則需要先進行其父類的初始化,虛擬機會保證一個類的構(gòu)造器方法在多線程環(huán)境中被正確加鎖和同步。當(dāng)訪問一個java類的靜態(tài)域時,只有真正申明這個靜態(tài)變量的類才會被初始化。

類的加載過程分為:類的主動引用和類的被動引用

類的主動引用(一定會發(fā)生類的初始化):


--new一個類的對象


--調(diào)用類的靜態(tài)成員(除了final常量)和靜態(tài)方法


--使用java.lang.reflect包的方法對類進行反射調(diào)用


--當(dāng)初始化一個類,如果父類沒有被初始化,先初始化其父類


--當(dāng)要執(zhí)行某個程序時,一定先啟動main方法所在的類


類的被動引用(不會發(fā)生類的初始化)


--當(dāng)訪問一個靜態(tài)變量時,只有真正聲明這個靜態(tài)變量的類才會初始化(通過子類引用父類的靜態(tài)變量,不會造成子類的初始化)


--通過數(shù)組定義類應(yīng)用,不會觸發(fā)此類的初始化A[] a = new A[10];


--引用常量(final類型)不會觸發(fā)此類的初始化(常量在編譯階段就存入調(diào)用類的常量池中了)


java中類的加載順序

虛擬機在首次加載java類時,會對靜態(tài)初始化塊、靜態(tài)成員變量、靜態(tài)方法進行一次初始化

只有在調(diào)用new方法時,才會創(chuàng)建類的實例

類實例創(chuàng)建過程:首先執(zhí)行父類的初始化塊部分,然后是父類的構(gòu)造方法,再執(zhí)行子類的初始化塊,最后是子類的構(gòu)造方法

類實例銷毀時,先銷毀子類部分,再銷毀父類部分。

java程序執(zhí)行過程

首先java源代碼文件(.java)會被java編譯為字節(jié)碼文件(.class),然后由jvm中的類加載器加載各個類的字節(jié)碼文件,加載完畢之后,交由jvm執(zhí)行引擎執(zhí)行。


jvm區(qū)域劃分

jvm區(qū)域可以根據(jù)線程分成線程隔離和線程共享兩個部分,其中線程隔離即這些區(qū)域是線程獨有的,每個線程都會分配這樣的區(qū)域,包括程序計數(shù)器、Java棧和本地方法棧;線程共享的有方法區(qū)和堆。


程序計數(shù)器(Program Counter Register)


由于在JVM中,多線程是通過線程輪流切換來獲得CPU執(zhí)行時間的,因此在任意具體時刻,一個CPU只會執(zhí)行一個線程中的指令,為了能夠使得每個線程都在線程切換或能夠恢復(fù)到切換之前的程序執(zhí)行位置,每個線程都需要有自己獨立的程序計數(shù)器,并且不能互相被干擾,否認(rèn)就會影響到程序的正常執(zhí)行次序。所以程序計數(shù)器是每個線程所私有的。在jvm規(guī)范中規(guī)定,如果線程執(zhí)行的是非native方法,則程序計數(shù)器中保存的是當(dāng)前需要執(zhí)行的指令的地址;如果線程執(zhí)行的是native方法,則程序計數(shù)器中保存的值是undefined。


java棧(vm stack)


java棧也稱為虛擬機棧(java vitual machine stack),java棧中存放的是一個個棧幀,每個棧幀對應(yīng)一個被調(diào)用的方法,在棧幀中包括局部變量表(local variables)、操作數(shù)棧(perand stack)、指向當(dāng)前方法所屬的類的運行時常量池的引用、方法返回地址和一些額外的附加信息。


本地方法棧(native method stack)


本地方法棧與java棧的作用和原理非常相似,只不過java棧是為執(zhí)行java方法服務(wù),而本地方法棧是為執(zhí)行本地方法服務(wù)的。在jvm規(guī)范中,并沒有對本地方法的具體實現(xiàn)方法以及數(shù)據(jù)結(jié)構(gòu)做強制規(guī)定,虛擬機可以自由實現(xiàn)它。在Hotsopt虛擬機中直接就把本地方法棧和java棧合二為一。


方法區(qū)(Method Area)


方法區(qū)在JVM中是一個非常重要的區(qū)域,與堆一樣是被線程共享的區(qū)域。在方法區(qū)中,存儲了每個類的信息(包括類的名稱、方法、字段信息)、靜態(tài)變量、常量以及編譯器編譯后的代碼。在方法區(qū)有一個非常重要的部分就是運行時常量池它是每一個類或者接口的常量池的運行時表示形式,在類和接口被加載到j(luò)vm后,對應(yīng)的運行時常量池就被創(chuàng)建出來。當(dāng)然并非Class文件常量池中的內(nèi)容才能進入運行時常量池,在運行期間,也可將新的常量放入運行時常量池中,比如String的intern方法。可以認(rèn)為方法區(qū)就是永久代。


堆(Heap)


java中的堆是用來存儲對象以及數(shù)組,數(shù)組的引用是存放在java棧中的。堆被所有線程共享,在jvm中只有一個堆。


在java中,堆被劃分成兩個不同的區(qū)域:新生代(Young)、老年代(Old)。


新生代又被劃分為三個區(qū)域:Eden和兩個幸存區(qū)。


這樣劃分的目的是為了使JVM能夠更好地管理堆內(nèi)存中的對象,包括內(nèi)存的分配及回收。


新生代主要存儲新創(chuàng)建的對象和尚未進入老年代的對象。老年代存儲經(jīng)過多次新生代GC(Minor GC)后仍然存活的對象。


方法區(qū)主要存放類與類之間關(guān)系的數(shù)據(jù),這部分?jǐn)?shù)據(jù)被加載到內(nèi)存以后,基本上不會發(fā)生變更,但是后期方法區(qū)也會被回收,回收的條件非常的苛刻;java堆中的數(shù)據(jù)基本上是朝生夕死的,用完之后就會被回收;java棧和本地方法棧中的數(shù)據(jù),滿足先進后出的原則,當(dāng)要獲取棧低的元素,必須把棧頂?shù)脑爻鰲?,回收率?00%;程序計數(shù)器是唯一一塊不會內(nèi)存溢出的區(qū)域。




引用

java中如果一個對象,沒有一個引用指向它,那么它就被認(rèn)為是一個垃圾。




java內(nèi)存管理分為內(nèi)存分配和內(nèi)存回收,不需要程序員參與。

垃圾回收機制主要看對象是否有引用指向。java對象的引用包括強引用、軟引用、弱引用和虛引用。

強引用:是指創(chuàng)建一個對象,并把這個對象賦給一個引用變量。強引用有引用變量指向時永遠(yuǎn)都不會被回收,即使內(nèi)存不足時。

軟引用:通過SoftReference類來實現(xiàn),當(dāng)系統(tǒng)內(nèi)存充足時,系統(tǒng)不會進行軟引用的內(nèi)存回收,軟引用的對象和強引用沒有太多區(qū)別,但是內(nèi)存不足時會回收軟引用的對象。

弱引用:通過WeakReference類來實現(xiàn),具有很強的不確定性,因為垃圾回收每次都會回收弱引用的對象。

虛引用:軟引用和弱引用都可以多帶帶使用,虛引用不能多帶帶使用,必須關(guān)聯(lián)引用隊列。虛引用的作用就是跟蹤對象被垃圾回收的狀態(tài),程序可以通過檢測與虛引用關(guān)聯(lián)的虛引用隊列是否已經(jīng)包含了指定的虛引用,從而了解虛引用對象是否即將被回收。它允許你知道對象何時從內(nèi)存中移除。

java中引用越弱表示對垃圾回收器的限制越少,對象越容易被回收。


垃圾回收

1、引用計數(shù)器算法:當(dāng)創(chuàng)建對象時,為這個對象在堆??臻g中分配地址,同時會產(chǎn)生一個引用計數(shù)器,同時引用計數(shù)器+1,當(dāng)有新的引用的時候,引用計數(shù)器繼續(xù)+1,而當(dāng)其中一個引用銷毀時,引用計數(shù)器-1,當(dāng)引用計數(shù)器被減為0的時候,標(biāo)志著這個對象已經(jīng)沒有引用了,可以被回收。但是當(dāng)代碼出現(xiàn)下面的情形時,該算法無法適用,objA指向objB,而objB又指向objA,這樣其他所有引用都消失了之后,objA和ObjB還是有一個相互的引用,無法回收,但實際上這兩個對象都已經(jīng)沒有額外的引用了,已經(jīng)是垃圾了。


ObjA.obj = ObjB;


ObjB.obj = ObjA;


2、根搜索算法(GC Root):把所有的引用關(guān)系看做一張圖,從一個節(jié)點GC Root開始,尋找對應(yīng)的引用節(jié)點,找到這個節(jié)點以后,繼續(xù)尋找這個節(jié)點的引用節(jié)點,當(dāng)所有的引用節(jié)點尋找完畢后,剩余的節(jié)點則被認(rèn)為是沒有被引用到的節(jié)點,即無用的節(jié)點。java中可作為GC Root的對象有:虛擬機棧中的引用對象、方法區(qū)中靜態(tài)屬性引用的對象、方法區(qū)中常量引用的對象、本地方法棧中引用的對象。


3、收集后的垃圾通過什么算法來回收?




標(biāo)記-清除算法:采用從根集合進行掃描,對存活的對象進行標(biāo)記,標(biāo)記完畢后,再掃描整個空間中未被標(biāo)記的對象,進行回收。標(biāo)記-清除算法不需要進行對象的移動,并且僅對不存活的對象進行處理,在存活對象比較多的情況下極為高效,但是由于標(biāo)記-清除算法直接回收不存活的對象,因此會造成內(nèi)存碎片。

復(fù)制算法(用于新生代):復(fù)制算法采用從根集合掃描,并將存活對象復(fù)制到一塊新的、沒有使用過的空間中,這種算法當(dāng)內(nèi)存中存活的對象比較少時,極為高效,但是帶來的成本是需要一塊內(nèi)存交換空間用于進行對象的移動。復(fù)制算法中,新生代中每次只使用Eden區(qū)和一塊幸存區(qū)存儲數(shù)據(jù),當(dāng)幸存區(qū)達(dá)到飽和狀態(tài)時,將幸存區(qū)的存活的對象移動到另一塊幸存區(qū)。

標(biāo)記-整理算法(用于老年代):標(biāo)記-整理算法和標(biāo)記-清除算法采用一樣的方式進行對象的標(biāo)記,但是清除時不同,在回收不存活的對象占用的空間后,會將所有存活的對象王左端空閑空間移動,并更新對應(yīng)的指針。解決了內(nèi)存碎片的問題。

分代回收機制:

新生代:絕大多數(shù)最新被創(chuàng)建的對象會被分配到這里,由于大部分對象在創(chuàng)建后會很快變得不可達(dá),所以很多對象被創(chuàng)建在新生代,然后消失。對象此區(qū)域消失的過程稱為“minor GC”.


一共有三個空間,其中包含一個伊甸園區(qū)(Eden)和兩個幸存區(qū)(survivor)。各空間執(zhí)行順序如下:


1、絕大多數(shù)剛剛被創(chuàng)建的對象會存放在伊甸園空間。


2、在伊甸園空間執(zhí)行了一次 GC后,存活的對象被移動到其中一個幸存者空間。


3、此后,在伊甸園空間執(zhí)行GC后,存活的對象會被堆積在同一個幸存者空間。


4、當(dāng)一個幸存者空間飽和戶,還在存活的對象會被移動到另一個幸存者空間,之后會清空已經(jīng)飽和的那個幸存者空間。


5、在以上的步驟中重復(fù)幾次依然存活的對象就會被移動到老年代。


老年代:對象沒有變的不可達(dá),并且從新生代中存活下來,就會被拷貝到這里,其所占的空間要比新生代多。也正是因為其相對較大的空間,發(fā)生在老年代上的GC要比新生代少得多。對象從老年代中消失的過程,稱為“major GC”。


永久代:也被稱為方法區(qū),用來保存類常量以及字符串常量。因此這個區(qū)域不是用來永久的存儲那些從老年代存活下來的對象。這個區(qū)域也可能發(fā)生GC,并且發(fā)生在這個區(qū)域上的GC時間也被稱為major GC.