運(yùn)行時(shí)數(shù)據(jù)區(qū)
- 堆:存放java對象實(shí)例庙楚,GC回收的地方,也稱GC堆
- 方法區(qū):堆的邏輯部分趴樱,存放靜態(tài)變量馒闷,常量酪捡,類信息
- 虛擬機(jī)棧:存放運(yùn)行時(shí)的java方法,局部變量纳账,(棧幀形式)
- 本地方法棧:類似虛擬機(jī)棧逛薇,服務(wù)的時(shí)native方法
- 程序計(jì)數(shù)器:線程切換,異常處理疏虫,指示下一行執(zhí)行的字節(jié)碼序號
虛擬機(jī)棧:
棧的FILO符合java方法間的調(diào)用
棧幀:
每個(gè)方法在執(zhí)行的時(shí)候會(huì)創(chuàng)建一個(gè)棧幀永罚,棧幀是虛擬機(jī)棧的棧元素,是虛擬機(jī)用于進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)卧秘;
- 操作數(shù)棧:字節(jié)碼指令的寫入和讀取呢袱,用于運(yùn)算和參數(shù)傳遞
- 局部變量表:用于存放參數(shù)和方法內(nèi)部定義的局部變量
- 動(dòng)態(tài)連接:
沒看懂
- 方法出口:方法返回的字節(jié)碼指令,返回到方法被調(diào)用的位置斯议;
當(dāng)一個(gè)方法被執(zhí)行時(shí)产捞,會(huì)創(chuàng)建一個(gè)棧幀(當(dāng)前棧幀),這個(gè)棧幀處于棧頂哼御,在編譯時(shí)期坯临,棧幀中需要的操作數(shù)棧和局部變量表的大小已經(jīng)完全確定,棧幀的內(nèi)存不會(huì)在運(yùn)行時(shí)受到影響恋昼;
- 方法中的參數(shù)和局部變量會(huì)存放到局部變量表中看靠,當(dāng)這個(gè)方法是非static方法時(shí),局部變量表中的第一個(gè)數(shù)據(jù)是這個(gè)方法的實(shí)例對象液肌;如果局部變量是一個(gè)對象實(shí)例挟炬,就會(huì)在局部變量表中以
reference引用
出現(xiàn),上圖的obj就是一個(gè)reference引用嗦哆,它的真是指向是堆中的Object對象谤祖; - 操作數(shù)棧:先將num入棧,再將1入棧老速,取出棧頂?shù)膬蓚€(gè)元素粥喜,相加再入棧,此時(shí)棧中只剩下num+1了橘券;再出棧额湘,將num+1這個(gè)數(shù)賦值給局部變量表中的num;上面的操作就是
num = num + 1
的實(shí)現(xiàn)旁舰;
動(dòng)態(tài)連接:
-
將虛方法的符號引用轉(zhuǎn)為直接引用:
在類加載的解析階段玛追,會(huì)將運(yùn)行時(shí)常量池的符號引用轉(zhuǎn)換為直接引用,這個(gè)這個(gè)操作叫做靜態(tài)解析
,但是它只會(huì)解析非虛方法(靜態(tài)方法恤筛,私有方法寿羞,構(gòu)造方法嘹狞,final修飾方法)
的符號引用,而棧幀中的動(dòng)態(tài)連接
就是在運(yùn)行時(shí)將其余的符號引用轉(zhuǎn)換為直接引用磺樱; -
分派(靜態(tài)分派纳猫,動(dòng)態(tài)分派):選擇
多態(tài)
的執(zhí)行版本
Person person = new Man();
在上述代碼中紧阔,Person類是person對象的靜態(tài)類型
,Man類是person對象的實(shí)際類型
续担,靜態(tài)類型和實(shí)際類型都可以發(fā)生改變,通過強(qiáng)轉(zhuǎn)改變靜態(tài)類型,重新new一個(gè)對象改變實(shí)際類型活孩,所以靜態(tài)類型的確定是在編譯期物遇,實(shí)際類型的確定在代碼運(yùn)行時(shí);
重載
是通過靜態(tài)類型
來判斷執(zhí)行版本憾儒,這個(gè)稱為靜態(tài)分派
重寫
是通過實(shí)際類型
來判斷執(zhí)行版本询兴,這個(gè)稱為動(dòng)態(tài)分派
棧幀數(shù)據(jù)的通信:
方法和方法之間的數(shù)據(jù)交互是通過使用同一塊內(nèi)存完成的,即棧幀A的本地變量表可能是棧幀B的操作數(shù)棧起趾,同一塊內(nèi)存對于不同的棧幀是不同的角色诗舰;
虛擬機(jī)對于棧的優(yōu)化:方法內(nèi)聯(lián)
方法A調(diào)用方法B,將方法B的代碼作為方法A的一部分來執(zhí)行训裆,從而避免創(chuàng)建棧幀眶根,減少棧內(nèi)存的消耗
類加載過程
上面的類加載過程中,加載边琉,驗(yàn)證属百,準(zhǔn)備,初始化变姨,卸載族扰,這五個(gè)步驟的順序是確定的,必須按照這個(gè)順序進(jìn)行定欧,解析階段在一些情況下可以在初始化之后進(jìn)行渔呵;
加載:
- 通過類的全限定名獲取定義這個(gè)類的二進(jìn)制字節(jié)流
- 將字節(jié)流代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
- 在內(nèi)存中生成一個(gè)這個(gè)類的class對象,作為方法區(qū)的訪問入口砍鸠;
獲取二進(jìn)制字節(jié)流這一步是開發(fā)者在類加載中可控性最強(qiáng)的階段(通過自定義classLoader)
驗(yàn)證(連接第一步):
目的:確保Class文件的字節(jié)流符合當(dāng)前虛擬機(jī)的要求扩氢,不會(huì)對虛擬機(jī)的安全產(chǎn)生威脅;class字節(jié)碼文件不一定通過.java源文件編譯來睦番,(動(dòng)態(tài)代理原理中是通過特定接口生成类茂,還可以通過網(wǎng)絡(luò)<Applet>,其他文件<JSP>等方式生成class字節(jié)碼)托嚣,所以巩检,通過其他途徑生成的字節(jié)碼文件,可以做java語言做不到的事情示启,這些可能導(dǎo)致系統(tǒng)的奔潰兢哭;
驗(yàn)證內(nèi)容:
- 文件格式驗(yàn)證:字節(jié)流是否符合Class文件格式規(guī)范
- 元數(shù)據(jù)驗(yàn)證:字節(jié)碼內(nèi)容的元數(shù)據(jù)信息是否符合java語言規(guī)范
- 字節(jié)碼驗(yàn)證:校驗(yàn)類的方法體在運(yùn)行是會(huì)不會(huì)危害虛擬機(jī)
- 符號引用驗(yàn)證:對類自身以外的信息校驗(yàn)
準(zhǔn)備(連接第二步):
為類的靜態(tài)變量
分配內(nèi)存(方法區(qū)
),并且初始化靜態(tài)變量為0/false/null夫嗓;
注意:這里是將static變量分配到方法區(qū)迟螺,而不是實(shí)例變量
冲秽,實(shí)例變量是在對象創(chuàng)建的時(shí)候分配在堆
中的;
解析(連接第三步):
將常量池中的符號引用轉(zhuǎn)為直接引用
符號引用:一組描述引用目標(biāo)的符號矩父,存放在Class文件的常量池中锉桑,符號引用和虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān),如果不經(jīng)過轉(zhuǎn)換無法得到真正的內(nèi)存地址窍株;
直接引用:能直接指向目標(biāo)的指針或間接定位到目標(biāo)的句柄民轴,直接引用和虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān),能直接找到內(nèi)存中的目標(biāo)球订;
https://cloud.tencent.com/developer/article/1450501
常量池種類:
-
class文件常量池:
編譯后后裸,class常量池存放 字面量 和 符號引用 -
全局字符串常量池:
類加載階段,在堆中創(chuàng)建字符串對象冒滩,對象的真實(shí)引用放入全局常量池微驶,1.7以后,全局字符串常量池被移動(dòng)到了堆內(nèi)开睡; -
運(yùn)行時(shí)常量池:
類加載階段(二進(jìn)制字節(jié)碼代表的靜態(tài)數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu))因苹,將class常量池的符號引用放入運(yùn)行時(shí)常量池, 解析階段,將運(yùn)行時(shí)常量池的符號引用轉(zhuǎn)為和全局常量池一致的直接引用 基本類型包裝類對象常量池:
初始化:
執(zhí)行類構(gòu)造器clinit()
,初始化類變量和其他資源士八,在準(zhǔn)備階段容燕,我們?yōu)轭愖兞吭诜椒▍^(qū)分配了內(nèi)存,并且將類變量置為0婚度,false蘸秘,/null ,在初始化階段會(huì)將類變量設(shè)置為代碼中的實(shí)際值蝗茁,如果類中有靜態(tài)代碼塊也會(huì)在這一階段執(zhí)行醋虏;
注意:
- clinit()是由編譯器自動(dòng)收集的所有類變量賦值行為和靜態(tài)代碼塊中的語句合并產(chǎn)生;
- 虛擬機(jī)保證子類的clinit()執(zhí)行前哮翘,一定會(huì)先執(zhí)行父類的clinit()颈嚼,所以第一個(gè)執(zhí)行的clinit()一定是Object類的;
- 如果一個(gè)類沒有賦值操作和靜態(tài)代碼塊饭寺,那就沒有必要執(zhí)行clinit()阻课,虛擬機(jī)就不會(huì)執(zhí)行
- 接口執(zhí)行clinit()前不需要執(zhí)行父類的clinit(),接口的實(shí)現(xiàn)類在初始化時(shí)也不會(huì)執(zhí)行接口的clinit()
- 虛擬機(jī)保證clinit()執(zhí)行時(shí)的線程安全(加鎖艰匙,其他線程阻塞限煞,釋放鎖)
對象創(chuàng)建過程
- 設(shè)置對象:對對象進(jìn)行必要的設(shè)置,設(shè)置HashCode员凝,GC分代年齡等
- init方法:初始化實(shí)例變量的值(代碼中設(shè)置的值)
對象內(nèi)存布局
- 對其填充其實(shí)是一個(gè)占位符沒有特別的意義署驻,對象大小必須是8字節(jié)的整數(shù)倍,通過對其填充來確保是8的整數(shù)倍;
對象訪問
句柄訪問:
直接訪問:
可達(dá)性分析法——判斷對象是否需要回收
可達(dá)性分析法:
- 可達(dá)性分析:如果一個(gè)對象到GCRoot對象沒有引用鏈旺上,則表明不可達(dá)瓶蚂;
GCRoot對象:
-
Java虛擬機(jī)棧
中的引用對象 -
本地方法棧
中JNI(Native)引用的對象 - 方法區(qū)中引用的
常量
,靜態(tài)屬性
對象
public class OOM {
private int rootConstant = 1000; // 方法區(qū)常量對象GCRoot
private static Object rootStatic = new Object(); // 方法區(qū)靜態(tài)對象GCRoot
private Object nonRoot = new Object(); // 類實(shí)例對象宣吱,非GCRoot
public void test (){
Object rootJavaStack = new Object(); // 虛擬機(jī)棧引用的對象GCRoot
// 可達(dá)窃这,不會(huì)回收
Object obj0 = rootConstant;
Object obj1 = rootStatic;
Object obj2 = rootJavaStack;
// 不可達(dá),回收
Object obj3 = nonRoot;
}
}
Java引用類型:
- 強(qiáng)引用 StrongReference:普通引用
- 軟引用 SoftReference:內(nèi)存不足時(shí)會(huì)回收軟引用對象(展示圖片使用內(nèi)存)
- 弱引用 WeakReference:放生GC時(shí)會(huì)回收弱引用對象(ThreadLocal的Entry使用內(nèi)存)
- 虛引用 PhantomReference:最弱的引用(日常開發(fā)用不到)
生存還是死亡:
其實(shí)在被認(rèn)定了不可達(dá)以后征候,還需要經(jīng)過兩次標(biāo)記篩選钦听;
- 一次標(biāo)記:如果一個(gè)對象到GCRoot沒有引用,則需要進(jìn)行一次標(biāo)記:是否有必要執(zhí)行finalize()倍奢,當(dāng)對象沒有覆蓋finalize()或者finalize()已經(jīng)被虛擬機(jī)執(zhí)行過,則表明沒有必要執(zhí)行垒棋;
- 二次標(biāo)記:如果對象有必要執(zhí)行finalize()卒煞,則進(jìn)行二次標(biāo)記:將對象放入一個(gè)F-Queue隊(duì)列,虛擬機(jī)創(chuàng)建一個(gè)優(yōu)先級特別低的線程Finalizer執(zhí)行隊(duì)列的對象的finalize方法叼架,在執(zhí)行前會(huì)再次判斷這個(gè)對象是否有到GCroot的引用鏈畔裕,如果還沒有,則真正回收乖订;
垃圾回收算法
復(fù)制算法:
特點(diǎn):
- 只能使用一半內(nèi)存
- 沒有內(nèi)存碎片
- 需要復(fù)制內(nèi)存
標(biāo)記清除:
特點(diǎn):
- 效率低
- 會(huì)出現(xiàn)內(nèi)存碎片
標(biāo)記整理:
特點(diǎn):
- 步驟較多
- 不會(huì)出現(xiàn)內(nèi)存碎片
分代收集算法:
將內(nèi)存分為新時(shí)代和老年代扮饶,新生代再分為Eden和Survivor(from to)區(qū)
- 對象優(yōu)先在Eden區(qū)分配
- 大對象直接進(jìn)入老年代
- 長期存活的對象進(jìn)入老年代:在Eden區(qū)中經(jīng)過一次Minor GC仍然存活就轉(zhuǎn)移到Survivor區(qū),在對象頭中存放的GC年齡+1乍构,每經(jīng)過一次Minor GC甜无,F(xiàn)rom和To就會(huì)發(fā)生一次復(fù)制,對象的年齡就會(huì)+1哥遮,默認(rèn)情況下岂丘,一個(gè)對象經(jīng)過15此Minor GC仍然存活就移動(dòng)到老年代;
- 動(dòng)態(tài)對象年齡判定:如果在Survivor區(qū)中的同一年齡的對象的內(nèi)存和大小大于Survivor(From/To)內(nèi)存的一半眠饮,就直接移動(dòng)到老年代奥帘;
GC發(fā)生的條件:空間不夠(Eden/老年代滿了,繼續(xù)放對象時(shí)仪召,會(huì)觸發(fā)GC)
堆 | 收集行為 | 算法 | 特點(diǎn) | 發(fā)生時(shí)刻 |
---|---|---|---|---|
新生代 | Minor GC | 復(fù)制算法 | 回收頻率高寨蹋,對象存活率低 | Eden區(qū)無可分配內(nèi)存 |
老年代 | Full GC | 標(biāo)記整理/ 標(biāo)記清除 | 回收頻率低,對象存活率高 | 老年代無可分配內(nèi)存 |
垃圾收集器()
吞吐量 = 業(yè)務(wù)線程運(yùn)行時(shí)間/總CPU時(shí)間
垃圾收集器 | 收集器類型 | 收集對象 | 算法 |
---|---|---|---|
Serial | 單線程 | 新生代 | 復(fù)制 |
ParNew | 并行的多線程 | 新生代 | 復(fù)制 |
Parallel Scavenge | 并行的多線程 | 新生代 | 復(fù)制 |
Serial Old | 單線程 | 老年代 | 標(biāo)記整理 |
Parallel Old | 并行的多線程 | 老年代 | 標(biāo)記整理 |
CMS |
并行并發(fā)的收集器 | 老年代 | 標(biāo)記清除 |
G1 |
并發(fā)并行的收集器 | 新生代&老年代 | 復(fù)制+標(biāo)記整理 |
CMS垃圾收集器:Concurrent Mark Sweep
步驟:
- 初始標(biāo)記
Stop the world
- 并發(fā)標(biāo)記
- 重新標(biāo)記
Stop the world
- 并發(fā)清除
特點(diǎn):
- 最短停頓回收時(shí)間:耗時(shí)最長并發(fā)標(biāo)記和并發(fā)清除可以同用戶線程一起運(yùn)行扔茅,總體上可以說CMS收集器是同用戶線程一起并發(fā)執(zhí)行的
- 降低總吞吐量:并發(fā)標(biāo)記和并發(fā)清除會(huì)占用CPU資源已旧,降低吞吐量
- 無法清除浮動(dòng)垃圾:在并發(fā)清除時(shí)用戶線程產(chǎn)生的垃圾稱為 "浮動(dòng)垃圾"
- 堆內(nèi)存不規(guī)整:標(biāo)記清除會(huì)導(dǎo)致堆內(nèi)存不規(guī)整;
G1收集器:Garbage First
最新咖摹、技術(shù)最前沿的垃圾收集器
步驟:
- 初始標(biāo)記
- 并發(fā)標(biāo)記
- 最終標(biāo)記
- 篩選回收
特點(diǎn):
不降低吞吐量的情況下減少停頓時(shí)間:可預(yù)測停頓
分代收集:可以回收新生代和老年代
不會(huì)產(chǎn)生內(nèi)存碎片