柚子快報(bào)邀請(qǐng)碼778899分享:JVM對(duì)象分配和垃圾回收機(jī)制
柚子快報(bào)邀請(qǐng)碼778899分享:JVM對(duì)象分配和垃圾回收機(jī)制
一、對(duì)象創(chuàng)建
1.1 符號(hào)引用
new 創(chuàng)建一個(gè)對(duì)象,需要在JVM創(chuàng)建對(duì)象。
符號(hào)引用:目標(biāo)對(duì)象采用一個(gè)符號(hào)表示,類(lèi)A加載的時(shí)候,如果成員變量類(lèi)B還沒(méi)有被加載進(jìn)來(lái),采用一個(gè)符號(hào)(字面量)來(lái)表示,這種引用就稱(chēng)為符號(hào)引用。
直接引用:真實(shí)地址。
檢查加載的時(shí)候,檢查類(lèi)B是否加載,已加載的話(huà),將符號(hào)引用修改為直接引用。
1.2 JVM創(chuàng)建對(duì)象過(guò)程
1)檢查加載,new指令創(chuàng)建對(duì)象,首先需要檢查對(duì)象對(duì)應(yīng)的Class類(lèi)是否已經(jīng)加載。未加載需要先加載類(lèi)。
2)分配內(nèi)存:在堆空間劃出一塊確定的內(nèi)存,分配給對(duì)象。因?yàn)镴AVA支持多線(xiàn)程,分配對(duì)象的需要考慮并發(fā)安全性。
3)內(nèi)存空間初始化:對(duì)象創(chuàng)建以后,成員初始值賦“零”值。
4)設(shè)置:
5)對(duì)象初始化
1.3 劃分內(nèi)存的方式
劃分內(nèi)存有兩種方式:指針碰撞和空閑列表。
指針碰撞:如果Java堆中內(nèi)存是絕對(duì)規(guī)整的,所有用過(guò)的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,那分配內(nèi)存就僅僅是把那個(gè)指針向空閑空間那邊挪動(dòng)一段與對(duì)象大小相等的距離,這種分配方式稱(chēng)為"指針碰撞"。
指針碰撞適用于Serial和ParNew等不會(huì)產(chǎn)生內(nèi)存碎片的垃圾收集器。 新生代通常使用指針碰撞進(jìn)行內(nèi)存分配。
空閑列表:如果Java堆中的內(nèi)存并不是規(guī)整的,已使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò),那就沒(méi)有辦法簡(jiǎn)單地進(jìn)行指針碰撞了,虛擬機(jī)就必須維護(hù)一個(gè)列表,記錄上哪些內(nèi)存塊是可用的,在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象實(shí)例,并更新列表上的記錄,這種分配方式稱(chēng)為"空閑列表"。
1.3.1 比較
指針碰撞效率比較高,采用指針碰撞的決定性因素:堆空間是否規(guī)整。
1.4?線(xiàn)程并發(fā)對(duì)象創(chuàng)建安全
Java支持多線(xiàn)程并發(fā),因此劃分內(nèi)存分配對(duì)象的時(shí)候,一定存在并發(fā)安全問(wèn)題。
CAS
線(xiàn)程分配內(nèi)存的時(shí)候,首先查詢(xún)空閑位置,然后通過(guò)CAS操作,采用CAS操作向空閑內(nèi)存位置申請(qǐng) 對(duì)應(yīng)對(duì)象大小的空間,如果申請(qǐng)成功,則分配成功;如果申請(qǐng)失敗,則說(shuō)明此空閑位置已經(jīng)被別的線(xiàn)程占據(jù),此位置已經(jīng)不是空閑位置。繼續(xù)上面的操作,查找下一個(gè)可用的空閑位置進(jìn)行分配。
CAS是一種無(wú)鎖機(jī)制,CPU指令,原子指令。?
TLAB
指本地線(xiàn)程分配緩沖(Thread Local Allocation Buffer,TLAB)
TLAB的目的是在為新對(duì)象分配內(nèi)存空間時(shí),讓每個(gè)Java應(yīng)用線(xiàn)程能使用自己專(zhuān)屬的分配指針來(lái)分配空間,減少同步開(kāi)銷(xiāo)。
TLAB只是讓每個(gè)線(xiàn)程擁有私有的分配指針,但底下存對(duì)象的內(nèi)存空間還是給所有線(xiàn)程訪(fǎng)問(wèn)的,只是其它線(xiàn)程無(wú)法在這個(gè)區(qū)域分配而已。當(dāng)一個(gè)TLAB用滿(mǎn)(分配指針top撞上分配極限end了),就新申請(qǐng)一個(gè)TLAB??臻g換時(shí)間
開(kāi)啟參數(shù) -XX+UseTLAB
1.5 對(duì)象的內(nèi)存空間分布
對(duì)象在內(nèi)存中的分配,包括 對(duì)象頭(MarkWork 8字節(jié)+ 類(lèi)型指針4字節(jié)),實(shí)例數(shù)據(jù)(對(duì)象成員)+對(duì)齊位。
1.5.1 對(duì)齊填充
對(duì)象要求是8個(gè)字節(jié)的整數(shù)倍,?當(dāng)對(duì)象分配空間不足8的倍數(shù)時(shí),自動(dòng)填充補(bǔ)齊。
1.5.2 數(shù)組對(duì)象的空間分布
?數(shù)組對(duì)象:對(duì)象頭(MarkWord 8字節(jié)+類(lèi)型指針4字節(jié))+數(shù)組長(zhǎng)度(4字節(jié))+對(duì)齊位。
數(shù)組對(duì)象與普通對(duì)象相比,多了一個(gè)數(shù)組長(zhǎng)度的字段,標(biāo)記數(shù)組長(zhǎng)度。
1.6 對(duì)象的訪(fǎng)問(wèn)定位
1.6.1 使用句柄訪(fǎng)問(wèn)對(duì)象
引用存儲(chǔ)的是一個(gè)地址,該地址是句柄的地址,而句柄是一種結(jié)構(gòu),分別存儲(chǔ) 實(shí)例指針和類(lèi)型指針 這兩種指針,(實(shí)例指針是指向堆中的對(duì)象實(shí)例,而類(lèi)型指針指向的是在方法區(qū)中該對(duì)象所屬類(lèi)型)。當(dāng)要訪(fǎng)問(wèn)對(duì)象時(shí),先通過(guò)引用訪(fǎng)問(wèn)句柄,再通過(guò)句柄訪(fǎng)問(wèn)對(duì)象實(shí)例以及對(duì)象類(lèi)型信息。句柄是存儲(chǔ)在堆中的,如果使用這種方式,那么就會(huì)從堆中分出一塊內(nèi)存用作句柄池。
1.6.2 使用直接指針訪(fǎng)問(wèn)對(duì)象
引用存儲(chǔ)的是對(duì)象實(shí)例在堆中的地址,通過(guò)引用可以直接訪(fǎng)問(wèn)對(duì)象實(shí)例。
比較
1)直接指針訪(fǎng)問(wèn)對(duì)象的優(yōu)點(diǎn):效率高,一次就可以找到對(duì)象。缺點(diǎn)是垃圾收集器移動(dòng)對(duì)象時(shí)需要修改引用,因?yàn)槔厥丈婕皩?duì)象移動(dòng),對(duì)象的實(shí)際地址會(huì)有變化。
2)句柄訪(fǎng)問(wèn)模式的優(yōu)點(diǎn):對(duì)象經(jīng)過(guò)多次移動(dòng)時(shí),虛擬機(jī)只需要修改句柄中的指向?qū)ο髮?shí)例的指針即可,不用修改引用。垃圾回收時(shí)效率高。缺點(diǎn)是需要額外維護(hù)對(duì)象池,訪(fǎng)問(wèn)對(duì)象效率低,需要兩個(gè)步驟才能得到對(duì)象實(shí)例。
目前虛擬機(jī)主要使用的直接指針訪(fǎng)問(wèn)的方式。因?yàn)樵L(fǎng)問(wèn)對(duì)象的頻率要遠(yuǎn)高于垃圾回收的頻率。
1.7 對(duì)象存活判斷
在JVM中,對(duì)象是自動(dòng)化的回收機(jī)制,用戶(hù)只需要管對(duì)象的創(chuàng)建,不要手動(dòng)去釋放對(duì)象,JVM垃圾收集器負(fù)責(zé)判斷哪些對(duì)象是垃圾,并且對(duì)垃圾對(duì)象的回收。
1.7.1 垃圾對(duì)象判斷算法
1.7.1.1 引用計(jì)數(shù)法
引用計(jì)數(shù)算法是通過(guò)判斷對(duì)象的引用數(shù)量來(lái)決定對(duì)象是否可以被回收。引用數(shù)為0時(shí),說(shuō)明對(duì)象沒(méi)有被其他對(duì)象引用,可回收。
實(shí)現(xiàn)方式:
給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值就加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值就減1;任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就是可被回收的對(duì)象。
優(yōu)點(diǎn):簡(jiǎn)單、高效
缺點(diǎn):很難處理循環(huán)引用,相互引用的兩個(gè)對(duì)象則無(wú)法釋放。因此目前主流的Java虛擬機(jī)都摒棄掉了這種算法。
python使用的引用計(jì)數(shù)法。對(duì)于循環(huán)引用怎么解決?通過(guò)開(kāi)啟一個(gè)額外線(xiàn)程檢測(cè)是否存在循環(huán)應(yīng)用,村換引用的對(duì)象特殊處理。
1.7.1.2 可達(dá)性分析算法
又叫根可達(dá)算法,從GC Roots(每種具體實(shí)現(xiàn)對(duì)GC Roots有不同的定義)作為起點(diǎn),向下搜索它們引用的對(duì)象,可以生成一棵引用樹(shù),樹(shù)的節(jié)點(diǎn)視為可達(dá)對(duì)象,反之視為不可達(dá)。引用樹(shù)上的節(jié)點(diǎn),都是可達(dá)的,需要存活。
1.7.1.2.1 GC Roots
GC Roots包括四類(lèi):靜態(tài)變量,虛擬機(jī)棧變量,常量池,JNI指針(JNI創(chuàng)建的對(duì)象)
這種情況下,即使存在循環(huán)引用,但是不在引用樹(shù)中,可以回收。?
除了這四種GC Roots,還存在其他的嗎?
內(nèi)部引用:Class對(duì)象、異常對(duì)象、類(lèi)加載器
內(nèi)部鎖:synchronized對(duì)象
內(nèi)部對(duì)象:JMXBean
臨時(shí)對(duì)象:跨代引用
1.8 Class對(duì)象回收的條件
Class對(duì)象回收的條件比較苛刻。滿(mǎn)足所有的條件:
1)Class new出的對(duì)象都要被回收掉;
2)對(duì)應(yīng)的類(lèi)加載也要被回收;
3)沒(méi)有通過(guò)反射使用Class類(lèi)
4)沒(méi)有 up-level 依賴(lài)的類(lèi)(即正在加載的類(lèi)引用了正在初始化的超類(lèi)或接口)。沒(méi)有被其他Class類(lèi)引用。
5)參數(shù)控制允許回收Class對(duì)象。
-Xnoclassgc: 應(yīng)用類(lèi)GC
當(dāng)您在啟動(dòng)時(shí)指定-Xnoclassgc時(shí),應(yīng)用程序中的類(lèi)對(duì)象在GC期間將保持不變,并且將始終被視為活動(dòng)的。這可能會(huì)導(dǎo)致更多的內(nèi)存被永久占用,如果不小心使用,會(huì)引發(fā)內(nèi)存不足異常。
1.9 對(duì)象的自我拯救Finalize
1.10 各種引用
強(qiáng)引用:存在引用時(shí),不會(huì)被會(huì)受到,即使內(nèi)存不足時(shí),會(huì)拋出OutOfMemary
軟引用:內(nèi)存不足時(shí)可以被回收掉
弱引用:只要執(zhí)行GC就可以被回收掉
虛引用:隨時(shí)被回收掉。
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
二、對(duì)象分配原則
幾乎所有的對(duì)象都在堆中分配。?
2.1 棧上對(duì)象分配
兩個(gè)條件:熱點(diǎn)代碼+逃逸分析
2.1.1 逃逸分析
對(duì)象的存活聲明周期能不能逃出這個(gè)方法。
分配在棧中的對(duì)象,不能被其他線(xiàn)程共享。
開(kāi)啟開(kāi)關(guān):?-XX:+DoEscapeAnalysis?
啟用逃逸分析。默認(rèn)是開(kāi)啟的。只有Java HotSpot Server VM支持這個(gè)選項(xiàng)。
2.2 大對(duì)象直接進(jìn)入老年代
只有Serial和ParNew這兩款垃圾收集器才生效。
啟動(dòng)條件:-XX:PretenureSizeThreshold=4m,
new的對(duì)象超過(guò)4M直接進(jìn)入老年代。
2.3 長(zhǎng)期存活的隊(duì)形進(jìn)入老年代
對(duì)象在新生代創(chuàng)建以后,結(jié)果一次垃圾收集,仍然存活的對(duì)象,age=age+1,年齡增長(zhǎng)1歲。當(dāng)年齡達(dá)到15時(shí),此對(duì)象需要進(jìn)階老年代。
允許設(shè)置最大年齡,不能超過(guò)15歲。
-XX:MaxTenuringThreshold=threshold
2.4 動(dòng)態(tài)年齡判斷
最大年齡太小的話(huà),導(dǎo)致對(duì)象不能盡可能的回收,快速進(jìn)入老年代,導(dǎo)致老年代臃腫;
年齡太大的話(huà),長(zhǎng)期存活的對(duì)象反復(fù)進(jìn)行回收檢測(cè),浪費(fèi)效率;
當(dāng)前存放對(duì)象的Surnvivor區(qū)域里(其中一塊區(qū)域,存放對(duì)象的那塊s區(qū)),一批對(duì)象的總大小大于這塊Sunvivor區(qū)域內(nèi)存大小的50%(由-XX:TargetSurvivorRatio參數(shù)指定),那么此時(shí)大于等于這批對(duì)象年齡最大值的對(duì)象,就可以直接進(jìn)入老年代了。
這個(gè)規(guī)則其實(shí)是希望那些可能是長(zhǎng)期存活的對(duì)象,盡早進(jìn)入老年代。 對(duì)象動(dòng)態(tài)年齡判斷機(jī)制一般是在minor gc之后觸發(fā)的。
2.5 空間分配擔(dān)保
為什么要設(shè)置老年代空間分配擔(dān)保機(jī)制?
空間擔(dān)保分配是指在發(fā)生Minor GC之前只要老年代的連續(xù)空間 大于 新生代對(duì)象總大小或者歷次晉升的平均大小 就會(huì)進(jìn)行Minor GC,否則將進(jìn)行Full GC。
內(nèi)存分配是在JVM在內(nèi)存分配的時(shí)候,新生代內(nèi)存不足時(shí),把新生代的存活的對(duì)象搬到老生代,然后新生代騰出來(lái)的空間用于為分配給最新的對(duì)象。這里老生代是擔(dān)保人。在不同的GC機(jī)制下,也就是不同垃圾回收器組合下,擔(dān)保機(jī)制也略有不同。在Serial+Serial Old的情況下,發(fā)現(xiàn)放不下就直接啟動(dòng)擔(dān)保機(jī)制;在Parallel Scavenge+Serial Old的情況下,卻是先要去判斷一下要分配的內(nèi)存是不是**>=Eden區(qū)大小的一半**,如果是那么直接把該對(duì)象放入老生代,否則才會(huì)啟動(dòng)擔(dān)保機(jī)制。
?2.6 分代垃圾收
2.6.1 什么是垃圾回收?
JAVA特有功能,垃圾收集意味著程序不再需要的對(duì)象是無(wú)用信息,這些信息將被丟棄回收。
2.6.2 分代垃圾回收
MinorGC/YoungGC: 新生代垃圾回收
MajorGC: 老年代垃圾回收,只有CMS
FullFC: 堆區(qū)域全部垃圾回收(新生代和老年代)
2.7 垃圾回收算法?
垃圾回收有三種主要算法:復(fù)制算法,
2.7.1 復(fù)制算法
原理:把內(nèi)存空間一份為二,一半用于保存對(duì)象,一半用于預(yù)留空間。當(dāng)進(jìn)行垃圾回收時(shí),將所有存活對(duì)象從工作空間復(fù)制到預(yù)留空間。復(fù)制完成后,對(duì)工作空間整體清理。
新生代絕大部分對(duì)象都是朝生夕死。
?
優(yōu)化復(fù)制算法,劃分Eden區(qū)。Eden區(qū):S1:S2=8:1:1,空間利用率能夠達(dá)到90%。
加強(qiáng)版的復(fù)制算法。
2.7.2 標(biāo)記清除算法
原理:采用可達(dá)性分析算法,標(biāo)記存貨對(duì)象,清理掉垃圾對(duì)象。這種清理算法會(huì)產(chǎn)生不連續(xù)的空閑內(nèi)存區(qū)域。
2.7.3 標(biāo)記-整理算法
原理:采用可達(dá)性分析算法標(biāo)記存活對(duì)象,對(duì)存活對(duì)象進(jìn)行整理,然后清理垃圾對(duì)象。?
對(duì)象移動(dòng)后,哈希值變化嗎?
會(huì)變化。
柚子快報(bào)邀請(qǐng)碼778899分享:JVM對(duì)象分配和垃圾回收機(jī)制
相關(guān)閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。