簡單介紹下JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)
Jvm在執(zhí)行Java程序的過程中會把它管理的內(nèi)存分為若干個(gè)不同的區(qū)域尔邓,這些部分有些是線程私有的刮吧,有些則是線程共享的。
線程私有的:程序計(jì)數(shù)器泽本,虛擬機(jī)棧,本地方法棧
線程共享的:方法區(qū)漾月,堆
簡單介紹下JVM常見異常
StackOverFlowError:當(dāng)線程請求棧的深度超過當(dāng)前 Java虛擬機(jī)棧的最大深度時(shí)
OutOfMemoryError:
1)當(dāng)線程請求棧時(shí)內(nèi)存用完了熊泵,無法再動態(tài)擴(kuò)展了
2)當(dāng)堆內(nèi)存或者永久代/元空間不夠,無法再分配對象或存放數(shù)據(jù)箕般,同時(shí)堆空間或用就代空間無法再拓展
3)當(dāng)垃圾回收器占用JVM的資源98%耐薯,同時(shí)回收效率不到2%
程序計(jì)數(shù)器
記錄當(dāng)前線程正在執(zhí)行的字節(jié)碼的地址或行號,主要作用是為了保證多線程情況下JVM程序的正常運(yùn)行
講一講方法區(qū)
方法區(qū)所有線程共享隘世,主要用于存儲類的信息可柿、常量池、方法數(shù)據(jù)丙者、方法代碼等
方法區(qū)邏輯上屬于堆的一部分复斥,但是為了與堆進(jìn)行區(qū)分,通常又叫“非堆”械媒。
JVM中對象的創(chuàng)建過程
虛擬機(jī)遇到一跳new指定時(shí)目锭,根據(jù)new的參數(shù)是否能在常量池中定位到一個(gè)類的符號引用
如果沒有,那就必須先執(zhí)行相應(yīng)的類加載過程
在類加載的檢查通過后纷捞,接下來虛擬機(jī)將為新生對象分配內(nèi)存痢虹,對象所需內(nèi)存大小在類加載完成后就可以完全確定,
為對象分配空間的任務(wù)等同于將一塊確定大小的內(nèi)存從Java堆中劃分出來,
真的就這么簡單嗎主儡?顯然不是奖唯,具體的實(shí)現(xiàn)是比較復(fù)雜,下面將描述完整的過程
1糜值、檢查加載
先執(zhí)行相應(yīng)的類加載過程丰捷,如果沒有,則進(jìn)行類加載
2寂汇、內(nèi)存分配
1)假如Java堆中內(nèi)醋是絕對規(guī)整的病往,所有用過的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊骄瓣,中間放著一個(gè)指針作為分界點(diǎn)的指示器
那所分配內(nèi)存就僅僅把那個(gè)指針向空閑那邊挪動一段與對象大小相等的距離
這種分配方式被稱為“指針碰撞”
2)如果Java堆中內(nèi)存不是規(guī)整的停巷,已使用的內(nèi)存和空間的內(nèi)存相互交錯,那么就沒有辦法簡單地進(jìn)行指針碰撞了
虛擬機(jī)必須維護(hù)一個(gè)列表,記錄哪些內(nèi)存是可用的畔勤,在分配的時(shí)候從列表找到一塊足夠大的空間劃分給對象實(shí)例蕾各,并更新列表上的記錄
這種分配方式稱為“空閑列表”
選擇哪種分配方式由Java堆是否規(guī)整決定,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定
比如使用CMS這種基于Mark-Sweep算法的收集器時(shí)硼被,Java堆中的內(nèi)存并不是規(guī)整的示损,通常采用空閑列表
3、內(nèi)存空間初始化
內(nèi)存分配完成后嚷硫,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為默認(rèn)值(如int默認(rèn)值為0检访,boolean為false等)
這一步操作保證了對象的實(shí)例字段在Java代碼中可以不賦初始值就能直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對應(yīng)的初始值
4仔掸、設(shè)置
接下來脆贵、虛擬機(jī)要對對象進(jìn)行必要的設(shè)置
假如這個(gè)對象是哪個(gè)類的實(shí)例,如何才能找到類的元數(shù)據(jù)信息起暮、對象的哈希碼卖氨、對象的GC分代年齡等信息,這些信息存放在對象的對象頭之中负懦。
5筒捺、對象初始化
以上4步完成后,從虛擬機(jī)的視角看纸厉,一個(gè)新的對象已經(jīng)產(chǎn)生了系吭,但從Java程序的視角來看,對象的創(chuàng)建才剛剛開始颗品,所有的字段都還為初始值肯尺,所以一般來說,執(zhí)行new指令后會接著把對象按照開發(fā)者的意愿進(jìn)行初始化躯枢,這個(gè)一個(gè)真正可用的對象才算完全產(chǎn)生出來则吟。
對象的訪問定位的兩種方式
句柄和直接指針兩種方式
句柄:Java堆中會劃分出一塊內(nèi)存來作為句柄池,reference中存儲的就是對象的句柄地址锄蹂,而句柄中包含了對象實(shí)例數(shù)據(jù)與數(shù)據(jù)類型各自的具體地址信息
直接指針:Java堆對象的布局中就必須考慮到如何放置訪問類型數(shù)據(jù)的相關(guān)信息氓仲,而reference中存儲的就是對象的地址。
HotSpot中使用直接指針得糜,使用直接指針訪問的方式最大的好處就是速度快敬扛,它節(jié)省了一次指針定位的時(shí)間開銷
JVM如何判斷對象是否可回收
可達(dá)性分析算法
這個(gè)算法的基本思想就是通過一系列的成為“GC Roots”的對象作為起點(diǎn),從這些借點(diǎn)開始向下搜索掀亩,節(jié)點(diǎn)所走過的路徑稱為引用鏈
當(dāng)一個(gè)對象到GC Roots沒有仁和應(yīng)用鏈相連,則證明這個(gè)對象不可用的欢顷。
簡單的介紹一下Java中的各種引用
1槽棍、強(qiáng)引用:
通常我們使用的大部分引用實(shí)際上都是強(qiáng)引用,這是使用普通的引用,如果一個(gè)對象具有強(qiáng)引用炼七,垃圾回收期絕不會回收它
當(dāng)內(nèi)存空間不足時(shí)缆巧,Java虛擬機(jī)則會拋出OutOfMemoryError錯誤,使程序異常終止豌拙。
2陕悬、軟引用:
如果一個(gè)對象只具有軟引用,當(dāng)內(nèi)存空間足夠時(shí)按傅,垃圾回收器就不會回收它捉超,但如果內(nèi)存空間不足時(shí),就會回收這些對象的內(nèi)存
只要垃圾回收器沒有回收它唯绍,該對象就可以被程序使用拼岳,軟引用可以用來實(shí)現(xiàn)內(nèi)存敏感的高速緩存。
軟引用可以和一個(gè)引用隊(duì)列(referenceQueue)聯(lián)合使用况芒;
如果軟引用所引用的對象被垃圾回收惜纸,Java虛擬機(jī)就會把這個(gè)軟引用加入與之關(guān)聯(lián)的引用隊(duì)列中。
3绝骚、弱引用
弱引用與軟引用的區(qū)別在于只具有軟引用的對象擁有更短暫的生命周期耐版。
在垃圾回收器線程掃描它管轄的內(nèi)存區(qū)域時(shí),不管內(nèi)存是否足夠压汪,都會將掃描到只具有弱引用的對象的內(nèi)存回收
不過由于垃圾回收是一個(gè)優(yōu)先級很低的線程粪牲,因此不一定會很快發(fā)現(xiàn)那些只有被弱引用的對象。
4蛾魄、虛引用
虛引用并不會決定對象的生命周期虑瀑,如果一個(gè)對象僅持有虛引用,在任何時(shí)候都有可能被垃圾回收滴须。
虛引用主要用來跟蹤對象被垃圾回收的活動舌狗。
程序設(shè)計(jì)中除了強(qiáng)引用,使用軟引用的情況較多扔水,這是因?yàn)檐浺每梢约铀貸VM對垃圾內(nèi)存的回收速度痛侍,可以維護(hù)系統(tǒng)的運(yùn)行安全,防止內(nèi)存溢出等問題產(chǎn)生魔市。
垃圾收集有哪些算法主届,各自的特點(diǎn)?
1待德、標(biāo)記-清除算法
標(biāo)記-清除算法分為“標(biāo)記”和“清除”階段君丁,首先標(biāo)記出所有需要回收的對象,在標(biāo)記完后統(tǒng)一回收所有被標(biāo)記的對象将宪。
它是最基礎(chǔ)的收集算法啊绘闷, 效率也很高橡庞,但是會帶來明顯的問題:
1)效率問題
2)空間問題(標(biāo)記清除后會產(chǎn)生大量不連續(xù)的碎片)
2、復(fù)制算法
為了解決效率問題印蔗,復(fù)制收集算法出現(xiàn)了扒最。
它可以將內(nèi)存分為大小相同的兩塊,每次使用其中的一塊华嘹,當(dāng)這一塊內(nèi)存使用完后吧趣,就將還存貨的對象復(fù)制到另一塊去,然后再把使用的空間一次清理掉耙厚。
這樣就使每次的內(nèi)存回收都是對內(nèi)存區(qū)間的一半進(jìn)行回收强挫。
3、標(biāo)記-整理算法
根據(jù)老年代的特點(diǎn)出的一種標(biāo)記算法颜曾,標(biāo)記過程仍然與“標(biāo)記-清除”算法一致
但后續(xù)步驟不是直接對可回收對象回收纠拔,而是讓所有存活的對象向一端移動,然后直接清理掉端邊界以外的內(nèi)存泛豪。
HotSpot為什么要分為新生代和老年代
將Java堆分為新生代和老年代稠诲,我們就可以根據(jù)各個(gè)年代的特點(diǎn),選擇合適的垃圾收集算法诡曙。
在新生代中臀叙,每次收集都會有大量對象死去,所以我們可以選擇復(fù)制算法价卤,只需要付出少量對象的復(fù)制成本就可以完成每次垃圾收集劝萤。
在老年代中,對象的存活幾率比較好慎璧,而且沒有額外的空間對它進(jìn)行分配擔(dān)保床嫌,所以我們必須選擇“標(biāo)記-清除”或“標(biāo)記-整理”算法進(jìn)行垃圾收集。
哪些情況需要對類進(jìn)行初始化胸私?
虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有些只有5種情況必須立即對類進(jìn)行初始化:
1厌处、使用new關(guān)鍵字實(shí)例化對象的時(shí)候、讀取或設(shè)置一個(gè)類的靜態(tài)字段時(shí)岁疼,已經(jīng)調(diào)用一個(gè)類的靜態(tài)方法時(shí)阔涉。
2、使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用時(shí)捷绒,如果類沒有初始化瑰排,則需要先觸發(fā)其初始化。
3暖侨、當(dāng)初始化一個(gè)類的時(shí)候椭住,如果發(fā)現(xiàn)其父類沒有被初始化,就會先初始化它的父親字逗。
4京郑、當(dāng)虛擬機(jī)啟動時(shí)显押,用戶需要指定一個(gè)要執(zhí)行的主類,虛擬機(jī)會先初始化這個(gè)類傻挂。
5、使用動態(tài)語言支持的時(shí)候
如果一個(gè)java.lang.invoke.MethodHanddle實(shí)例最后的解析結(jié)果REF_getStatic.REF_putstatic,REF_invokeStatic的方法句柄
并且這個(gè)方法句柄所對應(yīng)的類沒有進(jìn)行初始化挖息,而需要先觸發(fā)其初始化金拒。
雙親委派模型
雙親委派模型,要求除了頂層的啟動類加載器外套腹,其他的類加載器都應(yīng)該有自己的父類加載器绪抛。
這里的父子關(guān)系通常是子類通過組合關(guān)系而不是繼承關(guān)系來服用父類加載器的代碼。
雙親委派模型的工作過程:如果一個(gè)類加載器收到了類加載的請求
先把這個(gè)請求委派給父類加載器完成电禀,只有父類加載器反饋?zhàn)约簾o法完成加載請求時(shí)
子類加載器才回嘗試自己去加載幢码。