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

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


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


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


擴(kuò)展類加載器(extensions class loader)


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


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


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


自定義類加載器


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


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




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

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


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


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




類加載機(jī)制

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


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




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

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

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

類的加載過程分為:類的主動(dòng)引用和類的被動(dòng)引用

類的主動(dòng)引用(一定會(huì)發(fā)生類的初始化):


--new一個(gè)類的對(duì)象


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


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


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


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


類的被動(dòng)引用(不會(huì)發(fā)生類的初始化)


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


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


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


java中類的加載順序

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

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

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

類實(shí)例銷毀時(shí),先銷毀子類部分,再銷毀父類部分。

java程序執(zhí)行過程

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


jvm區(qū)域劃分

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


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


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


java棧(vm stack)


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


本地方法棧(native method stack)


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


方法區(qū)(Method Area)


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


堆(Heap)


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


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


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


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


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


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




引用

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




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

垃圾回收機(jī)制主要看對(duì)象是否有引用指向。java對(duì)象的引用包括強(qiáng)引用、軟引用、弱引用和虛引用。

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

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

弱引用:通過WeakReference類來實(shí)現(xiàn),具有很強(qiáng)的不確定性,因?yàn)槔厥彰看味紩?huì)回收弱引用的對(duì)象。

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

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


垃圾回收

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


ObjA.obj = ObjB;


ObjB.obj = ObjA;


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


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




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

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

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

分代回收機(jī)制:

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


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


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


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


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


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


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


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


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