對(duì)象的創(chuàng)建:
1.類加載檢查
虛擬機(jī)字節(jié)碼執(zhí)行引擎執(zhí)行jvm指令如果為new時(shí)皇拣,會(huì)查看方法區(qū)中常量池該類的符號(hào)引用,查看符號(hào)引用的類信息是否存在薄嫡,如果不存在氧急,則執(zhí)行類加載過(guò)程。
2.加載類
執(zhí)行類加載機(jī)制岂座,通過(guò)雙親委派機(jī)制态蒂,使用類加載器加載類文件,驗(yàn)證字節(jié)碼文件的完整性费什,將類中的靜態(tài)變量賦值為初始值,將字面量的符號(hào)引用轉(zhuǎn)換成直接引用手素,初始化賦值靜態(tài)成員變量鸳址。
3.分配內(nèi)存
分三種情況: 1.直接分配到old區(qū)(老年代) 2.分配到Eden區(qū) 3.分配在線程本地緩存區(qū) 分配內(nèi)存的方式: 1.指針碰撞(默認(rèn)使用)java堆中的內(nèi)存規(guī)整的情況下,會(huì)將用過(guò)的內(nèi)存和沒(méi)用過(guò)的內(nèi)存分開放置泉懦,將加載后計(jì)算好的類的大小稿黍,挪動(dòng)類的大小的指針; 2.不規(guī)整的情況下 空閑列表(Free List) 如果內(nèi)存為不規(guī)整的情況下崩哩,虛擬機(jī)就需要維護(hù)一個(gè)內(nèi)存可用的列表巡球,分配時(shí)從列表中查找,如果找到一塊可用的內(nèi)存邓嘹,進(jìn)行分配內(nèi)存酣栈,并更新列表的狀態(tài) 并發(fā)問(wèn)題: 1.cas+失敗重試: 在分配內(nèi)存時(shí)可能被其他線程搶占分配,虛擬機(jī)會(huì)采用CAS+失敗重試來(lái)保證內(nèi)存分配的同步和原子性; 2.TLAB(Thread Local Allocation Buffer)線程本地緩沖區(qū) 在Eden 區(qū)會(huì)給每個(gè)線程分配一塊小的內(nèi)存汹押,用來(lái)存儲(chǔ)對(duì)象矿筝,是線程非共享的,只能當(dāng)前線程訪問(wèn)棚贾,可使用--XX:+UseTLAB開啟窖维,但是默認(rèn)是開啟狀態(tài);-XX:TLABSize可指定大小
4.初始化
內(nèi)存分配后將對(duì)象實(shí)例變量初始化為初始值妙痹,例如int會(huì)初始為0铸史,String 會(huì)初始為null;保證變量可以不用賦值就可以進(jìn)行操作;
5.設(shè)置對(duì)象頭
類在初始化后怯伊,會(huì)給對(duì)象進(jìn)行設(shè)置琳轿,設(shè)置類的引用實(shí)例信息,還有在對(duì)象頭中設(shè)置對(duì)象的hash值,對(duì)象的GC年齡利赋,鎖信息等水评; 對(duì)象在內(nèi)存中的存儲(chǔ)分為三個(gè)部分: 1.對(duì)象頭 Object Header 對(duì)象頭中分為兩個(gè)部分,第一個(gè)為對(duì)象的運(yùn)行時(shí)數(shù)據(jù)媚送,包括對(duì)象的hashcode中燥,鎖的狀態(tài)信息,是否偏量鎖塘偎,對(duì)象被GC的分代年齡疗涉,第二個(gè)為類型指針,對(duì)類元信息的引用指針吟秩;(如果是數(shù)組對(duì)象的話咱扣,還有一個(gè)數(shù)組長(zhǎng)度的數(shù)據(jù)部分) 2.實(shí)例數(shù)據(jù) Instance Data 3.對(duì)象填充 Padding 個(gè)人理解:對(duì)象填充是優(yōu)化對(duì)象存取速度,假設(shè)對(duì)象占用3個(gè)字節(jié)涵防,會(huì)填充一個(gè)字節(jié)闹伪,改為為4個(gè)字節(jié),4個(gè)字節(jié)存儲(chǔ)起來(lái)速度是最優(yōu)的壮池; 指針壓縮: -XX:-UseCompressedOops 關(guān)閉指針壓縮偏瓤,1.6以后默認(rèn)開啟指針壓縮 32位系統(tǒng)一個(gè)對(duì)象指針占用的字節(jié)為4個(gè),而64位操作系占用的為8個(gè)字節(jié)椰憋,在移動(dòng)指針時(shí)性能較低厅克,所以采用指針壓縮方式,進(jìn)而減少GC的壓力橙依;
對(duì)象在內(nèi)存中的存儲(chǔ)結(jié)構(gòu)如下:
對(duì)象的內(nèi)存分配
1.對(duì)象在棧上分配機(jī)制
對(duì)象在分配內(nèi)存時(shí)证舟,如上圖所示,會(huì)先嘗試在棧上分配內(nèi)存窗骑,在分配之前女责,會(huì)有一個(gè)逃逸分析算法,他的作用呢也是優(yōu)化垃圾對(duì)象盡快進(jìn)入老年代的優(yōu)化慧域,大概邏輯為:判斷對(duì)象是否會(huì)被外部引用鲤竹,假設(shè)說(shuō)有如下方法: 方法1: public void createUser(){ User user = new User(); } 方法2: public User createUser(){ User user = new User(); return user; } 方法1中只是創(chuàng)建了User對(duì)象,并沒(méi)有對(duì)外返回User 對(duì)象昔榴,而方法2中將User返回了辛藻,調(diào)用方式時(shí),肯定外部會(huì)對(duì)該對(duì)象的引用互订;所以當(dāng)方式1執(zhí)行時(shí)吱肌,可能就會(huì)分配在棧中,方法執(zhí)行完 仰禽,對(duì)象就會(huì)被回收了氮墨,而方法二中會(huì)有返回值纺蛆,可能外部對(duì)它進(jìn)行引用,則不會(huì)向棧中分配规揪;jdk1.7以后默認(rèn)開啟逃逸分析桥氏; 全量替換: 當(dāng)對(duì)象在認(rèn)為在外部不會(huì)引用時(shí),進(jìn)一步分解時(shí)猛铅,虛擬機(jī)不會(huì)創(chuàng)建該對(duì)象字支,而是它的成員方法,變量分解為這個(gè)方法使用的成員變量奸忽,這樣就不會(huì)因?yàn)闂V袥](méi)有一塊連續(xù)的空間而無(wú)法分配內(nèi)存堕伪。 -XX:+EliminateAllocations jdk1.7以后默認(rèn)開啟全量替換;
2.tlab線程本地緩存區(qū)
如上圖栗菜,如果會(huì)被外部所引用的情況下欠雌,虛擬機(jī)會(huì)首先判斷是否為大對(duì)象,如果不是大對(duì)象的話疙筹,會(huì)嘗試在Eden區(qū)中線程獨(dú)有的一塊區(qū)域中分配富俄;
3.對(duì)象在Eden區(qū)分配機(jī)制
如果為大對(duì)象的話,會(huì)在Eden區(qū)進(jìn)行分配內(nèi)存腌歉,當(dāng)Eden區(qū)的內(nèi)存不足以存放該對(duì)象時(shí)蛙酪,會(huì)出發(fā)minorGC/youngGc 年輕代的GC。 GC分為兩種GC翘盖,一種是minorGC/youngGC,一種是majorGC/Full GC; minorGC負(fù)責(zé)對(duì)年輕代內(nèi)存區(qū)域進(jìn)行回收凹蜂; majorGC負(fù)責(zé)老年代內(nèi)存區(qū)域進(jìn)行回收馍驯;
4.老年代分配擔(dān)保機(jī)制
minorGC 后會(huì)計(jì)算老年代的內(nèi)存空間,如果設(shè)置了一個(gè)參數(shù)“-XX:-HandlePromotionFailure” 玛痊,會(huì)判斷之前minorGC后的平均內(nèi)存如果大于老年代汰瘫,如果大于的話會(huì)直接執(zhí)行一次majorGC/Full GC,然后在執(zhí)行minorGC。
5.對(duì)象動(dòng)態(tài)年齡判斷
個(gè)人理解:動(dòng)態(tài)年齡判斷機(jī)制擂煞,也就是說(shuō)混弥,在Eden區(qū)和survivor1區(qū)域gc復(fù)制對(duì)象時(shí)中假設(shè)說(shuō)Survivor有三批對(duì)象,第一批對(duì)象占用10%对省,GC年齡為8蝗拿,第二批對(duì)象占20%,GC年齡也為8蒿涎,第三批 占用51%哀托,通過(guò)虛擬機(jī)參數(shù)-XX:TargetSurvivorRatio可配置大小劳秋;它就會(huì)把第一批和第二批對(duì)象放進(jìn)老年代仓手;這個(gè)規(guī)則是想把對(duì)象盡早進(jìn)入老年代胖齐,也是對(duì)jvm垃圾回收的一個(gè)優(yōu)化;
6.大對(duì)象直接進(jìn)入老年代
虛擬機(jī)為了優(yōu)化垃圾回收嗽冒,大對(duì)象會(huì)直接被分配到老年代呀伙,有個(gè)虛擬機(jī)參數(shù)-XX:PretenureSizeThreshold 可設(shè)置對(duì)象大小標(biāo)準(zhǔn),如果超過(guò)這個(gè)標(biāo)準(zhǔn)會(huì)直接分配到老年代添坊,而不會(huì)存放到年輕代剿另,只針對(duì)于Serial和Parnew兩個(gè)收集器的情況下有效;什么是大對(duì)象帅腌,需要連續(xù)的一塊內(nèi)存驰弄,例如字符串和數(shù)組;
7.長(zhǎng)期存活的對(duì)象進(jìn)入老年代
虛擬機(jī)給每個(gè)對(duì)象都標(biāo)記了年齡速客,當(dāng)在Eden區(qū)被gc后可以放到survivor區(qū)戚篙,這個(gè)對(duì)象接下來(lái)每次gc都會(huì)年齡+1,直到達(dá)到闕值后就會(huì)被放到老年代,通過(guò)-XX:MaxTenuringThreshold參數(shù)可以設(shè)置對(duì)象可存活的年齡溺职。默認(rèn)為15歲岔擂,CMS收集器的闕值為6歲。
對(duì)象的內(nèi)存回收
1.引用計(jì)數(shù)器
這個(gè)算法是效率最高的浪耘,不過(guò)也有缺點(diǎn)乱灵,他的原理是,給對(duì)象添加一個(gè)計(jì)數(shù)器七冲,每有一個(gè)引用就會(huì)計(jì)數(shù)器+1痛倚,如果釋放了引用,就會(huì)-1澜躺;如果已經(jīng)沒(méi)有被引用后蝉稳,他的計(jì)數(shù)器就會(huì)為0,就會(huì)被gc掉掘鄙,可以又有這么一個(gè)問(wèn)題: 有這么一段代碼:思考 user1 和 user2 是否計(jì)數(shù)器會(huì)變?yōu)?耘戚; User user1 = new User(); User user2 = new User(); user1.property = user2; user2.property = user1; user 1 = null; user2 = null; user1 和user2的計(jì)數(shù)器是不會(huì)變?yōu)?的,以為他們相互引用操漠;所以說(shuō)基本上這個(gè)算法不會(huì)用上收津;
2.可達(dá)性分析算法
理解這么一個(gè)概念GC Root ,什么是GC Root,可以理解為祖宗浊伙,他是最高級(jí)別的對(duì)象撞秋,這個(gè)算法的原理就是,在執(zhí)行g(shù)c時(shí)吧黄,會(huì)掃描部服,棧中的對(duì)象,類的靜態(tài)變量拗慨,本地方法棧廓八, 從GC Root開始查找引用的對(duì)象奉芦,如果有對(duì)該對(duì)象的引用,該GC Root就不會(huì)被標(biāo)記為垃圾對(duì)象剧蹂,其他的都會(huì)被標(biāo)記為垃圾對(duì)象声功,等待被垃圾收集器所回收。如下圖所示:
擴(kuò)充:
如何判斷是一個(gè)無(wú)用的類
方法區(qū)主要回收無(wú)用的類分為三種:
1.堆中的對(duì)象已經(jīng)被回收掉了
2.加載該類的classloader已經(jīng)被回收了
3.該類的java.lang.Class 對(duì)象已經(jīng)沒(méi)有存在引用關(guān)系宠叼,并且沒(méi)有用反射獲取方法和字段