《深入理解JAVA虚拟机》——学习笔记
JVM內(nèi)存模型以及分區(qū)
JVM內(nèi)存分為:
1.方法區(qū):線程共享的區(qū)域,存儲已經(jīng)被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)
2.堆:線程共享的區(qū)域,存儲對象實例,以及給數(shù)組分配的內(nèi)存區(qū)域也在這里。
3.虛擬機棧:線程隔離的區(qū)域,每個線程都有自己的虛擬機棧,生命周期和線程相同。虛擬機棧描述方法執(zhí)行的內(nèi)存模型,以站棧幀為單位,每個棧幀存儲和方法運行有關(guān)的局部變量表、操作數(shù)棧、動態(tài)鏈接、方法返回地址等信息。
4.程序計數(shù)器:線程隔離的區(qū)域,每個線程都有自己的程序計數(shù)器,存儲程序當(dāng)前執(zhí)行的字節(jié)碼的行號。
5.本地方法棧:線程隔離,和虛擬機棧類似,是虛擬機調(diào)用Native方法時使用的。
?
堆的分區(qū),以及各個分區(qū)的特點:
Java堆是垃圾收集器管理的主要區(qū)域,按照分代收集算法的劃分,堆內(nèi)存空間可以繼續(xù)細分為年輕代,老年代。年輕代又可以劃分為較大的Eden區(qū),兩個同等大小的From Survivor,To Survivor區(qū)。默認的Eden區(qū)和Survivor區(qū)的大小比例為8:1:1,這個比例可以調(diào)節(jié)。在為新創(chuàng)建的對象分配內(nèi)存的時候先將對象分配到Eden區(qū)和From Survivor區(qū),在立即回收時,會將Eden區(qū)和Survivor區(qū)還存活的對象復(fù)制到To Survivor區(qū)中,如果To Survivor區(qū)的大小不能容納存活的對象,會把存活的對象分配到老年區(qū)。總體來說,新創(chuàng)建的小對象會放在年輕代,年輕代的對象大多在下一次垃圾回收時被回收,老年代存儲大的對象和存活時間長的對象。
?
對象的創(chuàng)建方法,對象的內(nèi)存布局,對象的訪問定位
對象的創(chuàng)建:
1.普通對象的創(chuàng)建過程:虛擬機遇到一條new指令時,首先檢查這個指令的參數(shù)(類的類型)是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類時候已經(jīng)被加載、解析、初始化過,如果沒有要執(zhí)行類加載過程。
2.數(shù)組對象的創(chuàng)建:虛擬機遇到一條newarray字節(jié)碼指令會在內(nèi)存中直接分配一塊區(qū)域。
3.Class對象的創(chuàng)建:在虛擬機加載類的時候,通過類的全限定名獲取此類的二進制字節(jié)流,再通過文件驗證后把字節(jié)流代表的靜態(tài)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu),并且在內(nèi)存中生成一個代表這個類的Class對象,存在方法區(qū)中,作為這個類的各種數(shù)據(jù)的訪問入口。
?
對象的內(nèi)存布局:
對象在內(nèi)存中的布局分為三塊區(qū)域:對象頭、實例數(shù)據(jù)、對齊填充
對象頭:存儲對象自身的運行時數(shù)據(jù),包括哈希嗎,GC分代年齡,鎖狀態(tài)標(biāo)識,線程持有的鎖,偏向線程ID,偏向時間戳等;對象頭的另外一部分是類型指針,即對象指向它在方法區(qū)中的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定該對象是哪個類的實例。如果對象是一個數(shù)組,對象頭還有一塊用語記錄數(shù)組長度的數(shù)據(jù)。
實例數(shù)據(jù):對象真正存儲的有效信息,是在類中定義的各種類型的字段內(nèi)容。
對齊填充:虛擬機要求對象的大小必須是8字節(jié)的整數(shù)倍,對齊填充起占位符的作用,保證對象大小為8字節(jié)的整數(shù)倍。
?
對象的訪問定位:Java程序通過棧上的引用數(shù)據(jù)操作堆中的具體對象,對象訪問方式有兩種:句柄訪問,直接指針訪問。
句柄訪問:Java堆劃分出一塊區(qū)域用作句柄池,引用中存儲對象的句柄地址,句柄中才實際包含著對象實例數(shù)據(jù)和對象類型數(shù)據(jù)各自的具體地址信息。
直接指針訪問:棧中的引用直接指向?qū)ο笤诙阎械牡刂?#xff0c;對象在頭數(shù)據(jù)中指向方法區(qū)中其類元數(shù)據(jù)的地址。
使用句柄的好處是引用中存儲的是穩(wěn)定的句柄地址,在對象被移動(垃圾回收導(dǎo)致對象的移動)時只會改變局并重的實例數(shù)據(jù)指針。使用直接指針訪問的好處是速度更快。
?
垃圾回收的判定方法:引用計數(shù)法,引用鏈法
引用計數(shù)法:給對象添加一個引用計數(shù)器,有對象引用計數(shù)器加1,引用失效計數(shù)器減1,計數(shù)器為0表示對象不再被使用,可以被回收。
引用鏈法(可達性分析):通過GC Roots作為起點,當(dāng)一個對象到到GC Roots沒有任何引用鏈相連時,證明對象時不可用的。
可作為GC Roots的對象是虛擬機棧中引用的對象、本地方法棧中引用的對象、方法區(qū)中類靜態(tài)屬性引用的對象,方法區(qū)中常量引用的對象(執(zhí)行上下文和全局性引用)
?
Java的四種引用類型及特點:
1.強引用:程序中普遍存在的,類似“String s=”hello wold””這類的引用,強引用的對象不會被回收。
2.軟引用:有用但是非必須的對象在系統(tǒng)將要發(fā)生內(nèi)存溢出之前會對軟引用的對象進行垃圾回收,SoftReference類實現(xiàn)軟引用。
3.弱引用:非必須的對象,被弱引用關(guān)聯(lián)的對象只能存活到下一次垃圾收集發(fā)生之前。
4.虛引用:最弱的引用關(guān)系,不能通過虛引用取得對象的實例,為對象設(shè)置虛引用的唯一目的就是在這個對象被收集器回收時收到一個系統(tǒng)通知。
四種引用強度依次減弱,強軟弱虛。
?
GC的三種收集算法的原理和特點,用途,優(yōu)化思路
三種垃圾收集算法:復(fù)制算法,標(biāo)記-清除算法、標(biāo)記-整理算法
標(biāo)記-清除算法:首先標(biāo)記出所有需要回收的對象,標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。缺點:標(biāo)記和清除兩個過程效率都不高;標(biāo)記清楚后會產(chǎn)生空間碎片,空間碎片導(dǎo)致分配較大對象時可能提前出發(fā)垃圾回收。
復(fù)制算法:將可用內(nèi)存分為兩個區(qū)域,每次只使用其中一塊,當(dāng)使用的那一塊內(nèi)存用完時,將還存活的對象復(fù)制到另外一塊內(nèi)存中,然后把已使用過的內(nèi)存空間一次清理掉。優(yōu)點:解決的空間碎片問題,實現(xiàn)簡單。缺點:將內(nèi)存縮小為兩塊,內(nèi)存使用率不高。復(fù)制操作頻繁效率變低。
標(biāo)記-整理算法:可回收對象標(biāo)記后,讓所有存活的對象向一端移動,然后清理掉邊界以外的內(nèi)存。優(yōu)點:不會產(chǎn)生空間碎片,比復(fù)制算法提高了內(nèi)存空間利用率。
復(fù)制算法用在年輕代的垃圾回收中,標(biāo)記整理和標(biāo)記清除算法用在老年代垃圾回收的收集器中。
?
GC收集器有哪些?CMS和G1收集器的特點
GC收集器按照回收區(qū)域不同,新生代有Serial,Parnew,Paralell Scanvage,老年代有Serial Old,CMS,Parallel old,還有新生代老年代通用的G1;
Serial 和Serial old是早期jdk中發(fā)布的垃圾收集器,特點是都為單線程,新生代采用復(fù)制算法,老年代采用標(biāo)記整理算法,兩個垃圾收集器在工作的時候必須要停掉所有的用戶線程,直到收集完成后才能回復(fù)用戶線程,由于是單線程工作方式,沒有線程交互的開銷所以能夠活的最高的單線程收集效率,使用在client模式下的虛擬機。
ParNew收集器是Serial收集器的多線程版本,是年輕代的垃圾收集器,可以和Serial old以及CMS老年代收集器搭配使用。Parnew在單CPU環(huán)境中的性能沒有Serial好,因為單CPU環(huán)境下的多線程按照時間順序串行執(zhí)行,還要承擔(dān)線程間交互的額外開銷,不過在多cpu環(huán)境下,Parnew的性能就會好很多,是運行在server模式下的虛擬機首選的新生代收集器。
在jdk1.4時新推出的垃圾收集器是Parallel Scanvage 和對應(yīng)的Parallel Old,新生代基于復(fù)制算法,老年代基于標(biāo)記整理算法.Parallel Scanvage也是并行性的多線程收集器,它和Parnew 的區(qū)別在于兩者的關(guān)注點不同。Parnew關(guān)注于減少垃圾回收時用戶線程停頓的時間,而Parllel Scanvage 關(guān)注點事獲得最大的吞吐量,也就是CPU運行用戶代碼與CPU總消耗時間的比值。停頓時間短適合于和用戶有交互的程序,吞吐量高則可以高效的利用CPU時間,盡快完成運算任務(wù),主要是和在后臺運算不需要太多的交互任務(wù)。
Jdk1.5時推出了能夠和用戶線程并發(fā)執(zhí)行的CMS收集器,CMS是老年代垃圾收集器。CMS是一種以獲取最短回收停頓時間為目標(biāo)的收集器,基于標(biāo)記清除算法來實現(xiàn)。它的工作過程先后分為初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)記、并發(fā)清除四個步驟,其中初始標(biāo)記和重新標(biāo)記是需要停頓用戶線程的,并發(fā)標(biāo)記和并發(fā)清理過程是可以和用戶線程并發(fā)執(zhí)行的,在整體垃圾收集時間里,初始標(biāo)記和重新標(biāo)記所占的時間很少,重新標(biāo)記階段又是可以多個垃圾回收線程并行執(zhí)行的,所以整體用戶線程停頓的時間很短。CMS的缺點:對CPU資源敏感,CMS默認啟動的垃圾回收線程數(shù)為(CPU數(shù)量+3)/4,在并發(fā)階段由于占用用戶線程導(dǎo)致應(yīng)用變慢,cpu不足4個時候?qū)τ脩舫绦蛴绊懞艽?#xff1b;CMS無法處理在并發(fā)清理階段新產(chǎn)生的垃圾,只有等下一次垃圾回收標(biāo)記后才能清除;CMS基于標(biāo)記清除算法會產(chǎn)生空間碎片,CMS的解決方式是在進行Full GC時開啟內(nèi)存整理,這一過程無法并發(fā),延長了用戶線程的停頓時間。
G1收集器是在jdk1.7時推出的用語新生代和來年代的垃圾收集器,面向server模式。G1把內(nèi)存區(qū)域劃分成多個大小相同的獨立區(qū)域,G1跟蹤每個區(qū)域里面垃圾堆積的價值大小,在后臺維護一個優(yōu)先列表,每次根據(jù)允許的收集時間,優(yōu)先回收價值最大的區(qū)域,這種收集策略可以在有限時間內(nèi)獲取盡可能高的收集效率。G1垃圾回收過程:初始標(biāo)記(單線程,停頓)、并發(fā)標(biāo)記(單線程,并發(fā))、最終標(biāo)記(多線程,并行,停頓)、篩選回收(多線程,并行,停頓)。
?
Minor GC和Full GC分別發(fā)生在什么時候?
當(dāng)創(chuàng)建對象分配的內(nèi)存空間不足時會啟動一次Minor GC,收集新生代的Eden區(qū)和From Survivor區(qū),把還存活的對象分配到To Survivor區(qū),如果To Survivor區(qū)的空間不足以容納存活的對象,會把存活的對象分配到老年代,如果老年代也沒有足夠的空間會啟動一次Full GC。
?
類加載過程:加載、驗證、準(zhǔn)備、解析、初始化
虛擬機的類加載機制就是把描述類的數(shù)據(jù)從Class文件(或者其他途徑)加載到內(nèi)存,并對數(shù)據(jù)進行校驗、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機直接使用的Java類型。
加載:1.通過一個類的全限定名獲取定義此類的二進制字節(jié)流2、將這個字節(jié)流所戴曉的靜態(tài)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)3、在內(nèi)存中生成一個代表這個類的Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。
驗證:1、文件格式驗證,保證輸入的字節(jié)流在格式上符合Class文件的格式規(guī)范,保證輸入的字節(jié)流能正確的解析,只有通過這個驗證,字節(jié)流才會存儲在方法區(qū)之內(nèi)2、元數(shù)據(jù)驗證,對類的元數(shù)據(jù)進行語義校驗,保證類描述的信息符合Java語言規(guī)范。比如驗證類的是否實現(xiàn)了父類或者接口中的方法等3、字節(jié)碼驗證,通過數(shù)據(jù)流和控制流的分析,確保類的方法符合邏輯,不會在運行時對虛擬機產(chǎn)生危害4、符號引用校驗,發(fā)生在解析階段,確保解析階段將符號引用轉(zhuǎn)化為直接飲用的正常執(zhí)行。
準(zhǔn)備:正式為類變量(static)分配內(nèi)存,并設(shè)置類變量初始值(數(shù)據(jù)類型的零值),這些變量所使用的內(nèi)存在方法區(qū)中分配。
解析:虛擬機將常量池內(nèi)的符號引用轉(zhuǎn)化為直接飲用,解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用點限定符7類符號引用進行。
初始化:初始化階段才真正執(zhí)行類中定義的Java代碼,初始化階段是執(zhí)行類構(gòu)造器<clinit>方法的過程。<clinit>方法(類構(gòu)造器)是由編譯器自動收集類中的靜態(tài)變量和靜態(tài)代碼塊合并產(chǎn)生的。子類和父類的初始化過程優(yōu)先級為:父類類構(gòu)造器->子類類構(gòu)造器->父類對象構(gòu)造函數(shù)->子類對象構(gòu)造函數(shù)。類中靜態(tài)類變量和靜態(tài)代碼塊是按照在類中定義的順序執(zhí)行的。
?
什么時候進行類的初始化?
JVM規(guī)定了有且僅有5中情況——對類進行主動引用,必須立即執(zhí)行類的初始化。
1)、遇到new,putstatic,getstatic,invokespecial四條字節(jié)碼指令的時候,如果沒有進行類的初始化要立即初始化。這四條字節(jié)碼指令對應(yīng)的編程中的環(huán)境為:使用new關(guān)鍵字實例化對象,讀取或設(shè)置類的靜態(tài)變量,調(diào)用類的靜態(tài)方法。
2)、使用java.lang.reflect包對類進行反射的時候,如果沒有初始化要立即初始化。
3)、初始化一個類的時候,如果其父類沒有進行初始化要先出發(fā)父類的初始化
4)、虛擬機啟動的時候,main方法所在的主類會被虛擬機先初始化
5)、使用動態(tài)語言在lava.lang.invoke.MethodHandle實例最后的解析結(jié)果是REF_getdtatic,REF_putStatic,REF_invokeStatic的方法句柄,這個句柄對應(yīng)的類沒有被初始化需要先觸發(fā)其初始化。
?
雙親委派模型:
類加載器的雙親委派模型是指從頂層到底層分別是啟動類加載器、擴展類加載器、應(yīng)用程序類加載器、自定義類加載器。類加載器之間的父子關(guān)系不是通過繼承來實現(xiàn),而是通過組合來實現(xiàn)。雙親委派模型的工作過程是:如果一個類加載器收到了類加載的請求,首先把這個請求委派給父類加載器去完成,所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中,只有當(dāng)父類反饋自己無法完成類加載請求的時候,自加載器才會嘗試自己去加載。
使用雙親委派模型的好處:java類隨著他的加載器一起具備了帶有優(yōu)先級的層次結(jié)構(gòu),最基礎(chǔ)的類由頂層的類加載器加載,這樣保證在程序中使用該類的地方使用的都是這同一個類。
?
轉(zhuǎn)載于:https://www.cnblogs.com/gl-developer/p/6502600.html
總結(jié)
以上是生活随笔為你收集整理的《深入理解JAVA虚拟机》——学习笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 201521123030《Java程序设
- 下一篇: 解决nginx重启“var/run/ng