工做之餘,想總結一下JVM相關知識。java
Java虛擬機在執行Java程序的過程當中會將其管理的內存劃分爲若干個不一樣的數據區域,這些區域有各自的用途、建立和銷燬的時間,有些區域隨虛擬機進程的啓動而存在,有些區域則是依賴用戶線程的啓動和結束來創建和銷燬。Java虛擬機所管理的內存包括如下幾個運行時數據區域,如圖:
一、程序計數器:指向當前線程正在執行的字節碼指令。線程私有的。
二、虛擬機棧:虛擬機棧是Java執行方法的內存模型。每一個方法被執行的時候,都會建立一個棧幀,把棧幀壓人棧,當方法正常返回或者拋出未捕獲的異常時,棧幀就會出棧。
(1)棧幀:棧幀存儲方法的相關信息,包含局部變量數表、返回值、操做數棧、動態連接
a、局部變量表:包含了方法執行過程當中的全部變量。局部變量數組所須要的空間在編譯期間完成分配,在方法運行期間不會改變局部變量數組的大小。
b、返回值:若是有返回值的話,壓入調用者棧幀中的操做數棧中,而且把PC的值指向 方法調用指令 後面的一條指令地址。
c、操做數棧:操做變量的內存模型。操做數棧的最大深度在編譯的時候已經肯定(寫入方法區code屬性的max_stacks項中)。操做數棧的的元素能夠是任意Java類型,包括long和double,32位數據佔用棧空間爲1,64位數據佔用2。方法剛開始執行的時候,棧是空的,當方法執行過程當中,各類字節碼指令往棧中存取數據。
d、動態連接:每一個棧幀都持有在運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程當中的動態連接。
(2)線程私有
三、本地方法棧:
(1)調用本地native的內存模型
(2)線程獨享。
四、方法區:用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯後的代碼等數據
(1)線程共享的
(2)運行時常量池:程序員
A、是方法區的一部分 B、存放編譯期生成的各類字面量和符號引用 C、Class文件中除了存有類的版本、字段、方法、接口等描述信息,還有一項是常量池,存有這個類的 編譯期生成的各類字面量和符號引用,這部份內容將在類加載後,存放到方法區的運行時常量池中。
五、堆(Heap):Java對象存儲的地方
(1)Java堆是虛擬機管理的內存中最大的一塊
(2)Java堆是全部線程共享的區域
(3)在虛擬機啓動時建立
(4)此內存區域的惟一目的就是存放對象實例,幾乎全部對象實例都在這裏分配內存。存放new生成的對象和數組
(5)Java堆是垃圾收集器管理的內存區域,所以不少時候稱爲「GC堆」算法
一、 Java的併發採用「共享內存」模型,線程之間經過讀寫內存的公共狀態進行通信。多個線程之間是不能經過直接傳遞數據交互的,它們之間交互只能經過共享變量實現。
二、 主要目的是定義程序中各個變量的訪問規則。
三、 Java內存模型規定全部變量都存儲在主內存中,每一個線程還有本身的工做內存。
(1) 線程的工做內存中保存了被該線程使用到的變量的拷貝(從主內存中拷貝過來),線程對變量的全部操做都必須在工做內存中執行,而不能直接訪問主內存中的變量。
(2) 不一樣線程之間沒法直接訪問對方工做內存的變量,線程間變量值的傳遞都要經過主內存來完成。
(3) 主內存主要對應Java堆中實例數據部分。工做內存對應於虛擬機棧中部分區域。數據庫
四、Java線程之間的通訊由內存模型JMM(Java Memory Model)控制。
(1)JMM決定一個線程對變量的寫入什麼時候對另外一個線程可見。
(2)線程之間共享變量存儲在主內存中
(3)每一個線程有一個私有的本地內存,裏面存儲了讀/寫共享變量的副本。
(4)JMM經過控制每一個線程的本地內存之間的交互,來爲程序員提供內存可見性保證。
五、可見性、有序性:
(1)當一個共享變量在多個本地內存中有副本時,若是一個本地內存修改了該變量的副本,其餘變量應該可以看到修改後的值,此爲可見性。
(2)保證線程的有序執行,這個爲有序性。(保證線程安全)
六、內存間交互操做:
(1)lock(鎖定):做用於主內存的變量,把一個變量標識爲一條線程獨佔狀態。
(2)unlock(解鎖):做用於主內存的變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其餘線程鎖定。
(3)read(讀取):做用於主內存變量,把主內存的一個變量讀取到工做內存中。
(4)load(載入):做用於工做內存,把read操做讀取到工做內存的變量載入到工做內存的變量副本中
(5)use(使用):做用於工做內存的變量,把工做內存中的變量值傳遞給一個執行引擎。
(6)assign(賦值):做用於工做內存的變量。把執行引擎接收到的值賦值給工做內存的變量。
(7)store(存儲):把工做內存的變量的值傳遞給主內存
(8)write(寫入):把store操做的值入到主內存的變量中
6.一、注意:
(1)不容許read、load、store、write操做之一單獨出現
(2)不容許一個線程丟棄assgin操做
(3)不容許一個線程不通過assgin操做,就把工做內存中的值同步到主內存中
(4)一個新的變量只能在主內存中生成
(5)一個變量同一時刻只容許一條線程對其進行lock操做。但lock操做能夠被同一條線程執行屢次,只有執行相同次數的unlock操做,變量纔會解鎖
(6)若是對一個變量進行lock操做,將會清空工做內存中此變量的值,在執行引擎使用這個變量前,須要從新執行load或者assgin操做初始化變量的值。
(7)若是一個變量沒有被鎖定,不容許對其執行unlock操做,也不容許unlock一個被其餘線程鎖定的變量
(8)對一個變量執行unlock操做以前,須要將該變量同步回主內存中數組
Java堆的內存劃分如圖所示,分別爲年輕代、Old Memory(老年代)、Perm(永久代)。其中在Jdk1.8中,永久代被移除,使用MetaSpace代替。
一、新生代:
(1)使用複製清除算法(Copinng算法),緣由是年輕代每次GC都要回收大部分對象。新生代裏面分紅一份較大的Eden空間和兩份較小的Survivor空間。每次只使用Eden和其中一塊Survivor空間,而後垃圾回收的時候,把存活對象放到未使用的Survivor(劃分出from、to)空間中,清空Eden和剛纔使用過的Survivor空間。
(2)分爲Eden、Survivor From、Survivor To,比例默認爲8:1:1
(3)內存不足時發生Minor GC
二、老年代:
(1)採用標記-整理算法(mark-compact),緣由是老年代每次GC只會回收少部分對象。
三、Perm:用來存儲類的元數據,也就是方法區。
(1)Perm的廢除:在jdk1.8中,Perm被替換成MetaSpace,MetaSpace存放在本地內存中。緣由是永久代進場內存不夠用,或者發生內存泄漏。
(2)MetaSpace(元空間):元空間的本質和永久代相似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。
四、堆內存的劃分在JVM裏面的示意圖:緩存
1、 判斷對象是否要回收的方法:可達性分析法
一、 可達性分析法:經過一系列「GC Roots」對象做爲起點進行搜索,若是在「GC Roots」和一個對象之間沒有可達路徑,則稱該對象是不可達的。不可達對象不必定會成爲可回收對象。進入DEAD狀態的線程還能夠恢復,GC不會回收它的內存。(把一些對象當作root對象,JVM認爲root對象是不可回收的,而且root對象引用的對象也是不可回收的)
二、 如下對象會被認爲是root對象:
(1) 虛擬機棧(棧幀中本地變量表)中引用的對象
(2) 方法區中靜態屬性引用的對象
(3) 方法區中常量引用的對象
(4) 本地方法棧中Native方法引用的對象
三、 對象被斷定可被回收,須要經歷兩個階段:
(1) 第一個階段是可達性分析,分析該對象是否可達
(2) 第二個階段是當對象沒有重寫finalize()方法或者finalize()方法已經被調用過,虛擬機認爲該對象不能夠被救活,所以回收該對象。(finalize()方法在垃圾回收中的做用是,給該對象一次救活的機會)
四、 方法區中的垃圾回收:
(1) 常量池中一些常量、符號引用沒有被引用,則會被清理出常量池
(2) 無用的類:被斷定爲無用的類,會被清理出方法區。斷定方法以下:
A、 該類的全部實例被回收
B、 加載該類的ClassLoader被回收
C、 該類的Class對象沒有被引用
五、 finalize():
(1) GC垃圾回收要回收一個對象的時候,調用該對象的finalize()方法。而後在下一次垃圾回收的時候,纔去回收這個對象的內存。
(2) 能夠在該方法裏面,指定一些對象在釋放前必須執行的操做。安全
2、 發現虛擬機頻繁full GC時應該怎麼辦:
(full GC指的是清理整個堆空間,包括年輕代和永久代)
(1) 首先用命令查看觸發GC的緣由是什麼 jstat –gccause 進程id
(2) 若是是System.gc(),則看下代碼哪裏調用了這個方法
(3) 若是是heap inspection(內存檢查),多是哪裏執行jmap –histo[:live]命令
(4) 若是是GC locker,多是程序依賴的JNI庫的緣由數據結構
3、常見的垃圾回收算法:
一、Mark-Sweep(標記-清除算法):
(1)思想:標記清除算法分爲兩個階段,標記階段和清除階段。標記階段任務是標記出全部須要回收的對象,清除階段就是清除被標記對象的空間。
(2)優缺點:實現簡單,容易產生內存碎片
二、Copying(複製清除算法):
(1)思想:將可用內存劃分爲大小相等的兩塊,每次只使用其中的一塊。當進行垃圾回收的時候了,把其中存活對象所有複製到另一塊中,而後把已使用的內存空間一次清空掉。
(2)優缺點:不容易產生內存碎片;可用內存空間少;存活對象多的話,效率低下。
三、Mark-Compact(標記-整理算法):
(1)思想:先標記存活對象,而後把存活對象向一邊移動,而後清理掉端邊界之外的內存。
(2)優缺點:不容易產生內存碎片;內存利用率高;存活對象多而且分散的時候,移動次數多,效率低下多線程
四、分代收集算法:(目前大部分JVM的垃圾收集器所採用的算法):併發
思想:把堆分紅新生代和老年代。(永久代指的是方法區)
(1) 由於新生代每次垃圾回收都要回收大部分對象,因此新生代採用Copying算法。新生代裏面分紅一份較大的Eden空間和兩份較小的Survivor空間。每次只使用Eden和其中一塊Survivor空間,而後垃圾回收的時候,把存活對象放到未使用的Survivor(劃分出from、to)空間中,清空Eden和剛纔使用過的Survivor空間。
(2) 因爲老年代每次只回收少許的對象,所以採用mark-compact算法。
(3) 在堆區外有一個永久代。對永久代的回收主要是無效的類和常量
五、GC使用時對程序的影響?
垃圾回收會影響程序的性能,Java虛擬機必需要追蹤運行程序中的有用對象,而後釋放沒用對象,這個過程消耗處理器時間
六、幾種不一樣的垃圾回收類型:
(1)Minor GC:從年輕代(包括Eden、Survivor區)回收內存。
A、當JVM沒法爲一個新的對象分配內存的時候,越容易觸發Minor GC。因此分配率越高,內存愈來愈少,越頻繁執行Minor GC B、執行Minor GC操做的時候,不會影響到永久代(Tenured)。從永久代到年輕代的引用,被當成GC Roots,從年輕代到老年代的引用在標記階段直接被忽略掉。
(2)Major GC:清理整個老年代,當eden區內存不足時觸發。
(3)Full GC:清理整個堆空間,包括年輕代和老年代。當老年代內存不足時觸發
一、 Java對象建立過程:
(1)虛擬機遇到一條new指令時,首先檢查這個指令的參數可否在常量池中定位到一個類的符號引用,並檢查這個符號引用表明的類是否已經加載、鏈接和初始化。若是沒有,就執行該類的加載過程。
(2)爲該對象分配內存。
A、假設Java堆是規整的,全部用過的內存放在一邊,空閒的內存放在另一邊,中間放着一個指針做爲分界點的指示器。那分配內存只是把指針向空閒空間那邊挪動與對象大小相等的距離,這種分配稱爲「指針碰撞」
B、假設Java堆不是規整的,用過的內存和空閒的內存相互交錯,那就沒辦法進行「指針碰撞」。虛擬機經過維護一個列表,記錄哪些內存塊是可用的,在分配的時候找出一塊足夠大的空間分配給對象實例,並更新表上的記錄。這種分配方式稱爲「空閒列表「。
C、使用哪一種分配方式由Java堆是否規整決定。Java堆是否規整由所採用的垃圾收集器是否帶有壓縮整理功能決定。
D、分配對象保證線程安全的作法:虛擬機使用CAS失敗重試的方式保證更新操做的原子性。(實際上還有另一種方案:每一個線程在Java堆中預先分配一小塊內存,稱爲本地線程分配緩衝,TLAB。哪一個線程要分配內存,就在哪一個線程的TLAB上分配,只有TLAB用完並分配新的TLAB時,才進行同步鎖定。虛擬機是否使用TLAB,由-XX:+/-UseTLAB參數決定)
(3)虛擬機爲分配的內存空間初始化爲零值(默認值)
(4)虛擬機對對象進行必要的設置,例如這個對象是哪一個類的實例、如何才能找到對象的元數據信息、對象的Hash碼、對象的GC分代年齡等信息。這些信息存放在對象的對象頭中。
(5) 執行<init>方法,把對象按照程序員的意願進行初始化。
二、 對象的定位訪問的方式(經過引用如何去定位到堆上的具體對象的位置):
(1)句柄:使用句柄的方式,Java堆中將會劃分出一塊內存做爲做爲句柄池,引用中存儲的就是對象的句柄的地址。而句柄中包含了對象實例數據和對象類型數據的地址。
(2)直接指針:使用直接指針的方式,引用中存儲的就是對象的地址。Java堆對象的佈局必須必須考慮如何去訪問對象類型數據。
(3)兩種方式各有優勢:
A、使用句柄訪問的好處是引用中存放的是穩定的句柄地址,當對象被移動(好比說垃圾回收時移動對象),只會改變句柄中實例數據指針,而引用自己不會被修改。
B、使用直接指針,節省了一次指針定位的時間開銷。
三、HotSpot的GC算法實現:
(1)HotSpot怎麼快速找到GC Root?
HotSpot使用一組稱爲OopMap的數據結構。在類加載完成的時候,HotSpot就把對象內什麼偏移量上是什麼類型的數據計算出來,在JIT編譯過程當中,也會在棧和寄存器中哪些位置是引用。這樣子,在GC掃描的時候,就能夠直接知道哪些是可達對象了。
(2)安全點:
A、HotSpot只在特定的位置生成OopMap,這些位置稱爲安全點。
B、程序執行過程當中並不是全部地方均可以停下來開始GC,只有在到達安全點是才能夠暫停。
C、安全點的選定基本上以「是否具備讓程序長時間執行「的特徵選定的。好比說方法調用、循環跳轉、異常跳轉等。具備這些功能的指令纔會產生Safepoint。
(3)中斷方式:
A、搶佔式中斷:在GC發生時,首先把全部線程中斷,若是發現有線程不在安全點上,就恢復線程,讓它跑到安全點上。 B、主動式中斷:GC須要中斷線程時,不直接對線程操做,僅僅設置一個標誌,各個線程執行時主動去輪詢這個標誌,當發現中斷標記爲真就本身中斷掛起。輪詢標記的地方和安全點是重合的。
(5)安全區域:一段代碼片斷中,對象的引用關係不會發生變化,在這個區域中任何地方開始GC都是安全的。在線程進入安全區域時,它首先標誌本身已經進入安全區域,在這段時間裏,當JVM發起GC時,就不用管進入安全區域的線程了。在線程將要離開安全區域時,它檢查系統是否完成了GC過程,若是完成了,它就繼續前行。不然,它就必須等待直到收到能夠離開安全區域的信號。
四、 GC時爲何要停頓全部Java線程?
由於GC先進行可達性分析。可達性分析是判斷GC Root對象到其餘對象是否可達,假如分析過程當中對象的引用關係在不斷變化,分析結果的準確性就沒法獲得保證。
五、 CMS收集器:
(1)一種以獲取最短回收停頓時間爲目標的收集器。
(2)通常用於互聯網站或者B/S系統的服務端
(3)基於標記-清除算法的實現,不過更爲複雜,整個過程爲4個步驟:
A、初始標記:標記GC Root能直接引用的對象 B、併發標記:利用多線程對每一個GC Root對象進行tracing搜索,在堆中查找其下全部能關聯到的對象。 C、從新標記:爲了修正併發標記期間,用戶程序繼續運做而致使標誌產生變更的那一部分對象的標記記錄。 D、併發清除:利用多個線程對標記的對象進行清除
(4)因爲耗時最長的併發標記和併發清除操做都是用戶線程一塊兒工做,因此整體來講,CMS的內存回收工做是和用戶線程一塊兒併發執行的。
(5)缺點:
A、對CPU資源佔用比較多。可能由於佔用一部分CPU資源致使應用程序響應變慢。 B、CMS沒法處理浮動垃圾。在併發清除階段,用戶程序繼續運行,可能產生新的內存垃圾,這一部分垃圾出如今標記過程以後,所以,CMS沒法清除。這部分垃圾稱爲「浮動垃圾「 C、須要預留一部份內存,在垃圾回收時,給用戶程序使用。 D、基於標記-清除算法,容易產生大量內存碎片,致使full GC(full GC進行內存碎片的整理)
六、 對象頭部分的內存佈局:HotSpot的對象頭分爲兩部分,第一部分用於存儲對象自身的運行時數據,好比哈希碼、GC分代年齡等。另一部分用於指向方法區對象類型數據的指針。
七、 偏向鎖:偏向鎖偏向於第一個獲取它的線程,若是在接下來的執行過程,沒有其餘線程獲取該鎖,則持有偏向鎖的線程永遠不須要同步。(當一個線程獲取偏向鎖,它每次進入這個鎖相關的同步塊,虛擬機不在進行任何同步操做。當有另一個線程嘗試獲取這個鎖時,偏向模式宣告結束)
一、通常來講,當survivor區不夠大或者佔用量達到50%,就會把一些對象放到老年區。經過設置合理的eden區,survivor區及使用率,能夠將年輕對象保存在年輕代,從而避免full GC,使用-Xmn
設置年輕代的大小
二、對於佔用內存比較多的大對象,通常會選擇在老年代分配內存。若是在年輕代給大對象分配內存,年輕代內存不夠了,就要在eden區移動大量對象到老年代,而後這些移動的對象可能很快消亡,所以致使full GC。經過設置參數:-XX:PetenureSizeThreshold=1000000
,單位爲B,標明對象大小超過1M時,在老年代(tenured)分配內存空間。
三、通常狀況下,年輕對象放在eden區,當第一次GC後,若是對象還存活,放到survivor區,此後,每GC一次,年齡增長1,當對象的年齡達到閾值,就被放到tenured老年區。這個閾值能夠同構-XX:MaxTenuringThreshold
設置。若是想讓對象留在年輕代,能夠設置比較大的閾值。
四、設置最小堆和最大堆:-Xmx
和-Xms
穩定的堆大小堆垃圾回收是有利的,得到一個穩定的堆大小的方法是設置-Xms和-Xmx的值同樣,即最大堆和最小堆同樣,若是這樣子設置,系統在運行時堆大小理論上是恆定的,穩定的堆空間能夠減小GC次數,所以,不少服務端都會將這兩個參數設置爲同樣的數值。穩定的堆大小雖然減小GC次數,可是增長每次GC的時間,由於每次GC要把堆的大小維持在一個區間內。
五、一個不穩定的堆並不是毫無用處。在系統不須要使用大內存的時候,壓縮堆空間,使得GC每次應對一個較小的堆空間,加快單次GC次數。基於這種考慮,JVM提供兩個參數,用於壓縮和擴展堆空間。
(1)-XX:MinHeapFreeRatio
參數用於設置堆空間的最小空閒比率。默認值是40,當堆空間的空閒內存比率小於40,JVM便會擴展堆空間
(2)-XX:MaxHeapFreeRatio
參數用於設置堆空間的最大空閒比率。默認值是70, 當堆空間的空閒內存比率大於70,JVM便會壓縮堆空間。
(3)當-Xmx和-Xmx相等時,上面兩個參數無效
六、經過增大吞吐量提升系統性能,能夠經過設置並行垃圾回收收集器。
(1)-XX:+UseParallelGC
:年輕代使用並行垃圾回收收集器。這是一個關注吞吐量的收集器,能夠儘量的減小垃圾回收時間。
(2)-XX:+UseParallelOldGC
:設置老年代使用並行垃圾回收收集器。
七、嘗試使用大的內存分頁:使用大的內存分頁增長CPU的內存尋址能力,從而系統的性能。-XX:+LargePageSizeInBytes
設置內存頁的大小
八、使用非佔用的垃圾收集器。-XX:+UseConcMarkSweepGC
老年代使用CMS收集器下降停頓。
九、-XXSurvivorRatio=3
,表示年輕代中的分配比率:survivor:eden = 2:3
十、JVM性能調優的工具:
(1)jps(Java Process Status):輸出JVM中運行的進程狀態信息(如今通常使用jconsole)
(2)jstack:查看java進程內線程的堆棧信息。
(3)jmap:用於生成堆轉存快照
(4)jhat:用於分析jmap生成的堆轉存快照(通常不推薦使用,而是使用Ecplise Memory Analyzer)
(3)jstat是JVM統計監測工具。能夠用來顯示垃圾回收信息、類加載信息、新生代統計信息等。
(4)VisualVM:故障處理工具
1、 概念:類加載器把class文件中的二進制數據讀入到內存中,存放在方法區,而後在堆區建立一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類加載的步驟以下:
一、加載:查找並加載類的二進制數據(把class文件裏面的信息加載到內存裏面)
二、鏈接:把內存中類的二進制數據合併到虛擬機的運行時環境中
(1)驗證:確保被加載的類的正確性。包括:
A、類文件的結構檢查:檢查是否知足Java類文件的固定格式 B、語義檢查:確保類自己符合Java的語法規範 C、字節碼驗證:確保字節碼流能夠被Java虛擬機安全的執行。字節碼流是操做碼組成的序列。每個操做碼後面都會跟着一個或者多個操做數。字節碼檢查這個步驟會檢查每個操做碼是否合法。 D、二進制兼容性驗證:確保相互引用的類之間是協調一致的。
(2)準備:爲類的靜態變量分配內存,並將其初始化爲默認值
(3)解析:把類中的符號引用轉化爲直接引用(好比說方法的符號引用,是有方法名和相關描述符組成,在解析階段,JVM把符號引用替換成一個指針,這個指針就是直接引用,它指向該類的該方法在方法區中的內存位置)
三、初始化:爲類的靜態變量賦予正確的初始值。當靜態變量的等號右邊的值是一個常量表達式時,不會調用static代碼塊進行初始化。只有等號右邊的值是一個運行時運算出來的值,纔會調用static初始化。
2、雙親委派模型:
一、當一個類加載器收到類加載請求的時候,它首先不會本身去加載這個類的信息,而是把該
請求轉發給父類加載器,依次向上。因此全部的類加載請求都會被傳遞到父類加載器中,只有當父類加載器中沒法加載到所需的類,子類加載器纔會本身嘗試去加載該類。噹噹前類加載器和全部父類加載器都沒法加載該類時,拋出ClassNotFindException異常。
二、意義:
提升系統的安全性。用戶自定義的類加載器不可能加載應該由父加載器加載的可靠類。(好比用戶定義了一個惡意代碼,自定義的類加載器首先讓系統加載器去加載,系統加載器檢查該代碼不符合規範,因而就不繼續加載了)
三、定義類加載器:若是某個類加載器可以加載一個類,那麼這個類加載器就叫作定義類加載器
四、初始類加載器:定義類加載器及其全部子加載器都稱做初始類加載器。
五、運行時包:
(1)由同一個類加載器加載而且擁有相同包名的類組成運行時包
(2)只有屬於同一個運行時包的類,才能訪問包可見(default)的類和類成員。做用是 限制用戶自定義的類冒充核心類庫的類去訪問核心類庫的包可見成員。
六、加載兩份相同的class對象的狀況:A和B不屬於父子類加載器關係,而且各自都加載了同一個類。
3、特色:
一、全盤負責:當一個類加載器加載一個類時,該類所依賴的其餘類也會被這個類加載器加載到內存中。
二、緩存機制:全部的Class對象都會被緩存,當程序須要使用某個Class時,類加載器先從緩存中查找,找不到,才從class文件中讀取數據,轉化成Class對象,存入緩存中。
3、 類加載器:
兩種類型的類加載器:
一、 JVM自帶的類加載器(3種):
(1)根類加載器(Bootstrap):
a、C++編寫的,程序員沒法在程序中獲取該類
b、負責加載虛擬機的核心庫,好比java.lang.Object
c、沒有繼承ClassLoader類
(2)擴展類加載器(Extension):
a、Java編寫的,從指定目錄中加載類庫
b、父加載器是根類加載器
c、是ClassLoader的子類
d、若是用戶把建立的jar文件放到指定目錄中,也會被擴展加載器加載。
(3)系統加載器(System)或者應用加載器(App):
a、Java編寫的
b、父加載器是擴展類加載器
c、從環境變量或者class.path中加載類
d、是用戶自定義類加載的默認父加載器
e、是ClassLoader的子類
二、用戶自定義的類加載器:
(1)Java.lang.ClassLoader類的子類
(2)用戶能夠定製類的加載方式
(3)父類加載器是系統加載器
(4)編寫步驟:
A、繼承ClassLoader
B、重寫findClass方法。從特定位置加載class文件,獲得字節數組,而後利用defineClass把字節數組轉化爲Class對象
(5)爲何要自定義類加載器?
A、能夠從指定位置加載class文件,好比說從數據庫、雲端加載class文件
B、加密:Java代碼能夠被輕易的反編譯,所以,若是須要對代碼進行加密,那麼加密之後的代碼,就不能使用Java自帶的ClassLoader來加載這個類了,須要自定義ClassLoader,對這個類進行解密,而後加載。
問題:Java程序對類的執行有幾種方式:
一、 主動使用(6種狀況):
JVM必須在每一個類「首次 主動使用」的時候,纔會初始化這些類。
(1) 建立類的實例
(2) 讀寫某個類或者接口的靜態變量
(3) 調用類的靜態方法
(4) 同過反射的API(Class.forName())獲取類
(5) 初始化一個類的子類
(6) JVM啓動的時候,被標明啓動類的類(包含Main方法的類)
只有當程序使用的靜態變量或者靜態方法確實在該類中定義時,該能夠認爲是對該類或者接口的主動使用。
二、 被動使用:除了主動使用的6種狀況,其餘狀況都是被動使用,都不會致使類的初始化。
三、 JVM規範容許類加載器在預料某個類將要被使用的時候,就預先加載它。若是該class文件缺失或者存在錯誤,則在程序「首次 主動使用」的時候,才報告這個錯誤。(Linkage Error錯誤)。若是這個類一直沒有被程序「主動使用」,就不會報錯。
類加載機制與接口:
一、 當Java虛擬機初始化一個類時,不會初始化該類實現的接口。
二、 在初始化一個接口時,不會初始化這個接口父接口。
三、 只有當程序首次使用該接口的靜態變量時,才致使該接口的初始化。
ClassLoader:
一、 調用Classloader的loadClass方法去加載一個類,不是主動使用,所以不會進行類的初始化。
類的卸載:
一、 有JVM自帶的三種類加載器(根、擴展、系統)加載的類始終不會卸載。由於JVM始終引用這些類加載器,這些類加載器使用引用他們所加載的類,所以這些Class類對象始終是可到達的。
二、 由用戶自定義類加載器加載的類,是能夠被卸載的。
補充:
(1)JDK : Java Development Kit,開發的時候用到的類包。
(2)JRE : Java Runtime Environment,Java運行的基礎,包含運行時須要的全部類庫。
JVM虛擬機先將java文件編譯成class文件(字節碼文件),而後再將class文件轉換成全部操做系統都能運行的機器指令。