整理有誤煩請(qǐng)?jiān)u論區(qū)提醒,及時(shí)改進(jìn)~
一、JVM內(nèi)存模型:
- JVM 作用:實(shí)現(xiàn)跨平臺(tái)的基礎(chǔ),一次編譯,到處運(yùn)行。
-
JVM生命周期:隨程序開始而創(chuàng)建,結(jié)束而銷毀。
方法區(qū):共享內(nèi)存區(qū)域
1.存儲(chǔ)已經(jīng)被虛擬機(jī)加載的:類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼。
2.常量池:存放編譯期生成的各種字面量(常量,如:int a = 1; String str = "abc")和符號(hào)引用量(類和接口的全限定名、字段名和描述符、方法名描述符),基本數(shù)據(jù)類型(Double、Float浮點(diǎn)數(shù)沒有且沒必要實(shí)現(xiàn)常量池技術(shù))的常量池是-128到127之間,在這個(gè)范圍中的基本數(shù)據(jù)類的包裝類可以自動(dòng)拆箱,比較時(shí)直接比較數(shù)值大小。注:裝箱:自動(dòng)將基本數(shù)據(jù)類型轉(zhuǎn)換為包裝器類型;拆箱:自動(dòng)將包裝器類型轉(zhuǎn)換成基本類型,裝箱過程是通過調(diào)用包裝器的valueOf方法實(shí)現(xiàn)的,而拆箱過程是通過調(diào)用包裝器的 xxxValue方法實(shí)現(xiàn)的(xxx基本數(shù)據(jù)類型)。編譯期和運(yùn)行期都可以將常量放入常量池中,內(nèi)存有限,無法申請(qǐng)時(shí)拋出OOM異常。
1.好處:避免頻繁的創(chuàng)建和銷毀對(duì)象而影響系統(tǒng)性能、實(shí)現(xiàn)對(duì)象共享。堆:是JVM啟動(dòng)時(shí)創(chuàng)建的最大的一塊內(nèi)存區(qū)域(一個(gè)進(jìn)程有且僅有一個(gè)Java堆),被線程共享,用于存放「對(duì)象實(shí)例」和「數(shù)組」,堆內(nèi)存的空間在邏輯上是連續(xù)的,在物理地址上是不連續(xù)的,GC主要活動(dòng)區(qū)域,同樣也會(huì)拋出OutOfMemoryError。
1.JVM初始分配的堆內(nèi)存由-Xms指定,默認(rèn)是物理內(nèi)存的1/64;
2.JVM最大分配的堆內(nèi)存由-Xmx指定,默認(rèn)是物理內(nèi)存的1/4。
3.默認(rèn)空余堆內(nèi)存小于40%時(shí),JVM就會(huì)增大堆直到-Xmx的最大限制;
4.空余堆內(nèi)存大于70%時(shí),JVM會(huì)減少堆直到-Xms的最小限制。因此服務(wù)器一般設(shè)置-Xms、-Xmx 相等以避免在每次GC 后調(diào)整堆的大小。-
Java虛擬機(jī)棧:為Java方法(字節(jié)碼指令集)服務(wù)。
1.線程私有,生命周期同線程;
2.描述的是Java方法的內(nèi)存模型,每個(gè)方法執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,棧幀存儲(chǔ):「局部變量表」、「操作數(shù)棧」、「動(dòng)態(tài)鏈接」、「方法出口」等信息。
3.方法從調(diào)用到執(zhí)行結(jié)束,對(duì)應(yīng)此棧幀在虛擬機(jī)棧中入棧到出棧的過程。
4.「局部變量表」存放方法參數(shù)和方法內(nèi)部定義的局部變量:基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、reference類型(對(duì)象引用,操作堆上的具體對(duì)象,指向堆中句柄池里「到對(duì)象實(shí)例數(shù)據(jù)的指針」或「到對(duì)象類型數(shù)據(jù)的指針」來找到具體對(duì)象。注:對(duì)象實(shí)例數(shù)據(jù):對(duì)象中各個(gè)實(shí)例字段的數(shù)據(jù),對(duì)象類型數(shù)據(jù):對(duì)象的類型、父類、實(shí)現(xiàn)的接口、方法等)、returnAddress類型(指向一條字節(jié)碼指令的地址)。注意:對(duì)于成員變量和定義在方法外的對(duì)象的引用則存儲(chǔ)在堆中
5.容易出現(xiàn)的Error:超過棧最大深度:StackOverflowError(堆棧溢出:通過-Xss設(shè)置棧大小、遞歸、死循環(huán)容易造成此類問題)、無法申請(qǐng)足夠內(nèi)存:OutOfMemoryError(內(nèi)存溢出)。
6.虛擬機(jī)內(nèi)存有限,分配某一棧內(nèi)存過大會(huì)擠占其他線程空間、GC不涉及虛擬機(jī)棧 本地方法棧:為本地Native方法服務(wù)。
同樣會(huì)出現(xiàn)StackOverflowError(堆棧溢出)、OutOfMemoryError(內(nèi)存溢出)異常。程序計(jì)數(shù)器:記錄當(dāng)前程序執(zhí)行到哪一步了
1.特點(diǎn):唯一無OutOfMemoryError情況的區(qū)域;每個(gè)線程都有自己的計(jì)數(shù)器(線程私有);內(nèi)存空間很小可忽略不計(jì),也是運(yùn)行速度最快的存儲(chǔ)區(qū)域;
2.前提:一個(gè)CPU每次只能處理一個(gè)程序,CPU會(huì)為每個(gè)程序分配時(shí)間片,多線程在特定的時(shí)間段內(nèi)只會(huì)執(zhí)行其中某一個(gè)線程中的一條指令。CPU不停的做任務(wù)切換,導(dǎo)致大量的中斷和恢復(fù),為了保證準(zhǔn)確的記錄各個(gè)線程正在執(zhí)行的當(dāng)前字節(jié)碼指令地址,為每個(gè)線程分配獨(dú)立的計(jì)數(shù)器以避免相互干擾。
2.作用:任何時(shí)間一個(gè)線程都只有一個(gè)方法在執(zhí)行,也就是所謂的當(dāng)前方法。程序計(jì)數(shù)器會(huì)存儲(chǔ)當(dāng)前線程正在執(zhí)行的Java方法的JVM指令地址;如果是在執(zhí)行native方法,則是未指定值(undefned)。
它是程序控制流的指示器,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來完成。字節(jié)碼解釋器工作時(shí)就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令。
二、垃圾回收機(jī)制
※判斷對(duì)象是否可回收的機(jī)制:
-
引用計(jì)數(shù)法(reference counting):給對(duì)象添加引用計(jì)數(shù)器,缺點(diǎn)是對(duì)象循環(huán)引用則無法回收。JVM沒有使用此算法,而是使用「根搜索算法」GC Roots。
引用計(jì)數(shù)法 -
可達(dá)性分分析(GC Roots):通過一系列名為 ‘GC Roots’ 的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)出發(fā)所走過的路徑稱之為引用鏈,當(dāng)一個(gè)對(duì)象到 GC Roots 沒有任何引用鏈相連的時(shí)候說明對(duì)象沒有被引用了。解決了對(duì)象循環(huán)引用的問題,即使a和b相互引用,但只要從GC Roots無法到達(dá)a或b則都不屬于存活對(duì)象。缺點(diǎn)是在多線方程環(huán)境下其他線程可能更新了對(duì)象中的引用,導(dǎo)致誤報(bào)(將引用設(shè)為null):會(huì)讓此次GC無法回收此垃圾對(duì)象;導(dǎo)致漏報(bào)(將引用設(shè)為未訪問過的對(duì)象):錯(cuò)誤的回收了還在引用的對(duì)象,導(dǎo)致Java虛擬機(jī)崩潰。
好在判斷對(duì)象真正死亡需要標(biāo)記兩次,篩選條件是此對(duì)象是否有必要執(zhí)行 finalize() 方法。當(dāng)對(duì)象沒有覆蓋 finalize() 方法,或者 finalize() 方法已經(jīng)被虛擬機(jī)調(diào)用過,虛擬機(jī)將這兩種情況都視為“沒有必要執(zhí)行”。如果這個(gè)對(duì)象被判定為有必要執(zhí)行 finalize() 方法,那么這個(gè)對(duì)象就會(huì)放置在一個(gè)叫做 F-Queue 的隊(duì)列中,并在稍后由一個(gè)由虛擬機(jī)自動(dòng)建立的、低優(yōu)先級(jí)的 Finalizer 線程去執(zhí)行它。這里所謂的“執(zhí)行”是指虛擬機(jī)會(huì)出發(fā)這個(gè)方法,并不承諾或等待他運(yùn)行結(jié)束。finalize() 方法是對(duì)象逃脫死亡命運(yùn)的最后一次機(jī)會(huì),稍后 GC 將對(duì) F-Queue 中的對(duì)象進(jìn)行第二次小規(guī)模的標(biāo)記,如果對(duì)象要在 finalize() 中成功拯救自己 —— 只要重新與引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián)即可。finalize() 方法只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次。
GC Roots
※主流的垃圾收集算法:
-
標(biāo)記- 清除算法(mark-sweep):當(dāng)堆中有效內(nèi)存空間被耗盡時(shí),會(huì)停止整個(gè)程序然后進(jìn)行標(biāo)記(mark)和清除(sweep)工作。
1.標(biāo)記(mark)階段:從引用根節(jié)點(diǎn)開始遍歷,標(biāo)記所有被引用的對(duì)象并在對(duì)象頭(Object Header)中記錄為可達(dá)對(duì)象。注:Object Header:包含對(duì)象基本信息,如布局、GC狀態(tài)、同步狀態(tài)、hash code(由JVM計(jì)算的hash code)、數(shù)組長(zhǎng)度(前提是數(shù)組)
2.清除(sweep)階段:對(duì)堆內(nèi)存從頭到尾進(jìn)行 線性遍歷,如果發(fā)現(xiàn)object header中沒有標(biāo)記可達(dá)對(duì)象,則將其回收。
3.缺點(diǎn):效率低、GC進(jìn)行需要停掉整個(gè)程序、清理出來的空間不連續(xù),容易產(chǎn)生碎片。
標(biāo)記- 清除算法 -
復(fù)制-清除算法(copying):把內(nèi)存分為 From 和 To 兩個(gè)空間,對(duì)象一開始只在 From 空間分配,GC時(shí)把 From 空間中存活的對(duì)象復(fù)制到 To 空間,復(fù)制完成后 To 空間變?yōu)?From 空間,清除之前的 From 空間并變成 To空間,如此反復(fù)。
優(yōu)點(diǎn):1.只記錄存活對(duì)象,對(duì)比「標(biāo)記-清除算法」少了搜索整個(gè)堆內(nèi)存時(shí)間,效率相對(duì)高。2.少了碎片化內(nèi)存,空間連續(xù)。
缺點(diǎn):需要空閑出一半的內(nèi)存,內(nèi)存浪費(fèi)。
復(fù)制-清除算法(copying) -
標(biāo)記-壓縮算法(mark-compact):分為標(biāo)記和壓縮兩個(gè)階段,標(biāo)記階段同「標(biāo)記-清除算法」,然后遍歷數(shù)次堆進(jìn)行壓縮,移動(dòng)對(duì)象,把存活的對(duì)象重新填裝,使對(duì)象緊挨著,避免內(nèi)存碎片。
標(biāo)記-壓縮算法(mark-compact)
※JVM的垃圾回收策略:分代回收!合理劃分內(nèi)存區(qū)域并根據(jù)各個(gè)區(qū)域定制不同的回收算法。劃分區(qū)域如下圖:

分代回收機(jī)制流程:新創(chuàng)建的對(duì)象放入Eden區(qū),當(dāng)此區(qū)域的內(nèi)存使用到達(dá)閾值時(shí)觸發(fā)Young GC,然后將Eden區(qū)存活的對(duì)象復(fù)制到From區(qū),下次再觸發(fā)Young GC再將Eden區(qū)和From區(qū)存活的對(duì)象放入To區(qū),F(xiàn)rom區(qū)和To區(qū)頻繁的來回復(fù)制,存活次數(shù)過多的對(duì)象就會(huì)進(jìn)入Old老年區(qū)。
※GC類型:Scavenge GC和Full GC兩種類型。
- Scavenge GC:一般當(dāng)新對(duì)象生成,并且在Eden申請(qǐng)空間失敗時(shí),就好觸發(fā)Scavenge GC,對(duì)Eden區(qū)域進(jìn)行GC,清除非存活對(duì)象,并且把尚且存活的對(duì)象移動(dòng)到Survivor幸存區(qū)。然后整理Survivor的兩個(gè)區(qū)(From、To)。
- Full GC:對(duì)整個(gè)堆進(jìn)行整理,包括Young、Tenured(老年代)和Perm(永久代)。Full GC比Scavenge GC要慢,因此應(yīng)該盡可能減少Full GC。有如下原因可能導(dǎo)致Full GC:1.Tenured被寫滿、2.Perm被寫滿、3.System.gc()被顯示調(diào)用、4.上一次GC之后Heap的各域分配策略動(dòng)態(tài)變化。
※垃圾回收器:串行收集器、并行收集器、并發(fā)收集器,選用哪種JVM會(huì)根據(jù)當(dāng)前系統(tǒng)配置進(jìn)行判斷:
- 串行處理器:使用單線程處理所有垃圾回收工作,因?yàn)闊o需多線程交互,所以效率比較高,使用-XX:+UseSerialGC打開。
適用情況:數(shù)據(jù)量比較小(100M左右);單處理器下并且對(duì)響應(yīng)時(shí)間無要求的應(yīng)用。
缺點(diǎn):只能用于小型應(yīng)用 - 并行處理器:對(duì)年輕代進(jìn)行并行垃圾回收,因此可以減少垃圾回收時(shí)間。一般在多線程多處理器機(jī)器上使用。使用-XX:+UseParallelGC.打開。
適用情況:“對(duì)吞吐量有高要求”,多CPU、對(duì)應(yīng)用響應(yīng)時(shí)間無要求的中、大型應(yīng)用。舉例:后臺(tái)處理、科學(xué)計(jì)算。
缺點(diǎn):應(yīng)用響應(yīng)時(shí)間可能較長(zhǎng) - 并發(fā)處理器:可以保證大部分工作都并發(fā)進(jìn)行(應(yīng)用不停止),垃圾回收只暫停很少的時(shí)間,此收集器適合對(duì)響應(yīng)時(shí)間要求比較高的中、大規(guī)模應(yīng)用。使用-XX:+UseConcMarkSweepGC打開。
適用情況:“對(duì)響應(yīng)時(shí)間有高要求”,多CPU、對(duì)應(yīng)用響應(yīng)時(shí)間有較高要求的中、大型應(yīng)用。舉例:Web服務(wù)器/應(yīng)用服務(wù)器、電信交換、集成開發(fā)環(huán)境。
※引用的回收:強(qiáng)>軟>弱>虛
強(qiáng)引用:Object obj = new Object(); 創(chuàng)建的,只要強(qiáng)引用在就不回收。
軟引用:SoftReference 類實(shí)現(xiàn)軟引用。在系統(tǒng)要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行二次回收。
弱引用:WeakReference 類實(shí)現(xiàn)弱引用。對(duì)象只能生存到下一次垃圾收集之前。在垃圾收集器工作時(shí),無論內(nèi)存是否足夠都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象。
虛引用:PhantomReference 類實(shí)現(xiàn)虛引用。無法通過虛引用獲取一個(gè)對(duì)象的實(shí)例,為對(duì)象設(shè)置虛引用唯一目的就是能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。
※GC優(yōu)化:
GC算法觸發(fā)時(shí),因?yàn)榫€程不安全,除GC所需的線程外,所有線程都進(jìn)入等待狀態(tài)(native 方法可以繼續(xù)執(zhí)行但也不能跟JVM交互),直到GC任務(wù)完成,這個(gè)階段叫做「stop-the-world」!所以GC優(yōu)化盡可能減少GC的觸發(fā)時(shí)機(jī)。
GC回收的區(qū)域位于「堆」和「方法區(qū)」內(nèi)的對(duì)象?!笚!估锏臄?shù)據(jù)在超出作用域后會(huì)被JVM自動(dòng)釋放掉。
三、類加載機(jī)制
概述:JVM把.class字節(jié)碼文件加載到虛擬機(jī)內(nèi)存中的過程。
- 我們編寫業(yè)務(wù)的.java文件,Java編譯器將其轉(zhuǎn)化為.class文件,JVM將class文件字節(jié)碼文件加載到內(nèi)存中, 并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)中的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),在堆(并不一定在堆中,HotSpot在方法區(qū)中)中生成一個(gè)代表這個(gè)類的java.lang.Class 對(duì)象,作為方法區(qū)類數(shù)據(jù)的訪問入口。
- 運(yùn)行時(shí)非全部一次性加載,而是按需加載。
-
一個(gè)class類通常只加載一次,之后從jvm的class實(shí)例的緩存中獲取而免去文件系統(tǒng)中加載.class文件了(??:JVM在執(zhí)行某段代碼時(shí)遇到class A,然后去緩存中找,找不到會(huì)去相應(yīng)的class文件找class A的類信息并加載到內(nèi)存中)。
Java從編碼到執(zhí)行
類加載過程:
類加載主要有三個(gè)過程:加載(loading)、連接(linking)、初始化(initializing)。其中連接又分三個(gè)步驟:校驗(yàn)、準(zhǔn)備和解析。
- 加載(Loading):把.class文件轉(zhuǎn)化成靜態(tài)數(shù)據(jù)結(jié)構(gòu),存儲(chǔ)在方法區(qū)內(nèi),并在堆中生成一個(gè)便用用戶調(diào)用的Java.lang.Class類型的對(duì)象
- 連接(Linking):
- 校驗(yàn)(verification):校驗(yàn)加載進(jìn)來的.class文件是否符合標(biāo)準(zhǔn),不符合標(biāo)準(zhǔn)拒絕加載到內(nèi)存。(這塊主要驗(yàn)證的是元數(shù)據(jù)和字節(jié)碼,為了確保此class文件是安全的,不會(huì)危害虛擬機(jī)的自身安全。)
- 準(zhǔn)備(preparation):將.class文件的靜態(tài)變量賦 初始值,初始值都為0,但被final修飾的初始值 = 默認(rèn)值,即聲明的值。(JDK8之前永久代實(shí)現(xiàn)方法區(qū),存放類的元信息、常量池、靜態(tài)變量等。JDK8改為元空間實(shí)現(xiàn)方法區(qū),存放類的元信息,而常量池和靜態(tài)變量則存在了堆中)
- 解析(resolution):把.class文件常量池中用到的符號(hào)引用轉(zhuǎn)化成直接內(nèi)存地址,可以訪問到的內(nèi)容。(此階段會(huì)驗(yàn)證符號(hào)引用)
-
初始化(initializing):成功初始化,此階段給靜態(tài)變量賦予初始值,
類加載過程
雙親委派機(jī)制:
解釋:當(dāng)有一個(gè)類需要被加載時(shí),首先要判斷這個(gè)類是否已經(jīng)被加載到內(nèi)存,判斷加載與否的過程是有順序的,如果有自己定義的類加載器,會(huì)先到custom class loader 的cache(緩存)中去找是否已經(jīng)加載,若已加載直接返回結(jié)果,否則到App的cache中查找,如果已經(jīng)存在直接返回,如果不存在,到Extension中查找,存在直接返回,不存在繼續(xù)向父加載器中尋找直到Bootstrap頂層,如果依然沒找到,那就是沒有加載器加載過這個(gè)類,需要委派對(duì)應(yīng)的加載器來加載,先看看這個(gè)類是否在自己的加載范圍內(nèi),如果是直接加載返回結(jié)果,若不是繼續(xù)向下委派,以此類推直到最下級(jí),如果最終也沒能加載,就會(huì)直接拋異常 ClassNotFoundException,這就是雙親委派模式。如下圖:


類加載順序
父類的靜態(tài)字段 -->父類的靜態(tài)代碼塊 -->子類靜態(tài)字段 -->子類靜態(tài)代碼塊 -->父類成員變量(非靜態(tài)字段) -->父類非靜態(tài)代碼塊 -->父類構(gòu)造器 -->子類成員變量 -->子類非靜態(tài)代碼塊 -->子類構(gòu)造器。











