一、java內(nèi)存模型
要深入了解jvm之前,需要對java內(nèi)存結(jié)構(gòu)有清楚的認(rèn)識,我們先通過一個簡單的demo來了解java內(nèi)存模型戳杀。
有如下兩個類,我們通過它的執(zhí)行順序來觀察內(nèi)存中的變化
class A{
static B b = new B();
public static void main(String[] args){
fun1();
fun2();
}
public static void fun1(){
B b1 = new B();
b1.load();
}
public static void fun2(){
b.load();
}
}
class B{
public void load(){}
}
當(dāng)class A中的main方法啟動的時候內(nèi)存中需要做的操作
1夭苗、加載類A,B到j(luò)ava內(nèi)存中
類加載到內(nèi)存中的時候,我們必然有一塊區(qū)域來存放這些加載的類的數(shù)據(jù)隔缀,這塊區(qū)域我們稱之為方法區(qū)题造,在jdk8之后方法區(qū)的概念被元空間(Metaspace)取代。元空間相比于方法區(qū)靈活很多猾瘸。比如可以動態(tài)分配元空間大小界赔,通過指針和其他方式來表示類和元數(shù)據(jù),可以更有效的利用內(nèi)存牵触。為了統(tǒng)一概念淮悼,我們接下來的介紹全部稱之為方法區(qū)。
如下圖揽思,會把A,B的字節(jié)碼文件加載到內(nèi)存中的方法區(qū)袜腥。
2、執(zhí)行main方法
類加載之后執(zhí)行main方法的時候也有一塊區(qū)域來對應(yīng)方法的執(zhí)行钉汗,稱之為棧羹令。棧的數(shù)據(jù)結(jié)構(gòu)決定了他的特點 先進(jìn)后出鲤屡。main方法執(zhí)行的時候需要先開啟一個main線程來執(zhí)行字節(jié)碼文件。而java又可以支持多線程福侈,此時就需要一個東西來記錄每個線程執(zhí)行到哪一行代碼酒来。這個記錄代碼行數(shù)的東西叫程序計數(shù)器。那么此時的內(nèi)存模型為:
當(dāng)main方法執(zhí)行的時候會調(diào)用fun1和fun2肪凛。在fun1中創(chuàng)建了一個對象b1堰汉,fun2中使用了靜態(tài)對象b(靜態(tài)對象的初始化隨著類的加載而加載,為了更好的延申出堆的概念故在此處提出)伟墙。這些對象的創(chuàng)建需要對應(yīng)一塊內(nèi)存區(qū)域翘鸭,此塊內(nèi)存區(qū)域就是堆。
此時的內(nèi)存模型為:
3远荠、方法執(zhí)行完畢釋放資源
當(dāng)所有方法執(zhí)行完之后矮固,方法需要出棧(又成彈棧),此時可以看到main方法在最下面(壓棧)譬淳,先進(jìn)后出因此fun2,fun1,main依次出棧档址。出棧之后棧里面內(nèi)存得到釋放,但是堆里面的對象此時b還是被static b引用邻梆,而b1此時沒有任何人引用守伸。所以b1對應(yīng)的堆內(nèi)存就成了垃圾需要被釋放。但是java的內(nèi)存回收機(jī)制并不是沒有引用就立馬會回收浦妄。當(dāng)堆內(nèi)存不足的時候才會觸發(fā)內(nèi)存回收機(jī)制尼摹,jvm才會開始回收這些內(nèi)存垃圾。
二剂娄、新生代蠢涝,老年代、永久代的概念
1阅懦、永久代:永久存在與jvm內(nèi)存中的數(shù)據(jù)和二。方法區(qū)就是永久代。
2耳胎、新生代:新創(chuàng)建的對象所在的位置惯吕。
還是看上圖的內(nèi)存模型,每次new出來的對象都會放到堆里面怕午,這批對象存儲的位置在堆中又有細(xì)分废登,分為新生代和老年代,新生代就是用于存方這些對象的地方郁惜。
3堡距、老年代:年紀(jì)大的對象。
當(dāng)堆內(nèi)存快要滿的時候,會觸發(fā)jvm垃圾回收機(jī)制吏颖,此時無引用的內(nèi)存垃圾就會被釋放掉搔体,有引用的對象會被保留,每經(jīng)過一次垃圾回收被保留下來的對象的年齡就會+1半醉。jvm默認(rèn)年齡>15的對象就會被移入老年代疚俱。還有一種情況會放入老年代,后面再講缩多。
三呆奕、Eden區(qū)和Survivore區(qū)
系統(tǒng)的內(nèi)存都是連續(xù)的,當(dāng)需要使用內(nèi)存的時候會在空閑內(nèi)存之后連續(xù)開辟一塊新的內(nèi)存以供使用衬吆。而jvm觸發(fā)垃圾回收機(jī)制時梁钾,回收新生代無引用的數(shù)據(jù),當(dāng)這些垃圾被回收之后就會出現(xiàn)一種情況逊抡,被留下的有引用的數(shù)據(jù)之間存在內(nèi)存碎片姆泻,此時就會導(dǎo)致內(nèi)存不再連續(xù)。會影響接下來的內(nèi)存分配使用冒嫡。因此衍生出了E區(qū)和S區(qū)的概念拇勃。
所有new出來的對象全部放到E區(qū),當(dāng)E區(qū)要滿的時候觸發(fā)新生代垃圾回收機(jī)制(Young GC 或者叫Minor GC)孝凌,此時會把E區(qū)所有垃圾全部釋放方咆,并且把存活下來的對象使用復(fù)制算法重新整理內(nèi)存放入到S區(qū),并把這批對象年齡+1蟀架。這樣就保證了E區(qū)的空間永遠(yuǎn)是連續(xù)的瓣赂。
但是這樣還有問題,第二次垃圾回收的時候如何處理片拍,因為此時S區(qū)被上一次存活的對象占用了空間煌集,這一批存活的對象該如何整理內(nèi)存,該放在哪里呢捌省?
對于這一問題牙勘,其實也很簡單,再增加一個S區(qū)即可所禀,永遠(yuǎn)空出來一個S區(qū)用于整理內(nèi)存和存放存活對象。因此放钦,堆內(nèi)存空間整體模型就出來了色徘。
四、哪些對象會被回收
大致了解了新生代模型之后我們來想一個問題操禀,jvm如何判斷什么數(shù)據(jù)需要被回收的褂策。
jvm每次內(nèi)存回收的時候都會采用一種 可達(dá)性分析算法 來判定該對象是否可以被回收。這個算法的意思就是分析誰在引用這個對象 一級一級向上找,看是否有GC Roots斤寂。
那什么又是GC Roots 呢耿焊,GC Roots有一下幾種
1、在虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象遍搞,譬如各個線程被調(diào)用的方法堆棧中使用到的參數(shù)罗侯、局部變量、臨時變量等溪猿。
2钩杰、在方法區(qū)中類靜態(tài)屬性引用的對象,譬如Java類的引用類型靜態(tài)變量诊县。
3讲弄、在方法區(qū)中常量引用的對象,譬如字符串常量池(String Table)里的引用依痊。
4避除、本地方法棧中 JNI(Native方法)引用的對象
5、Java虛擬機(jī)內(nèi)部的引用胸嘁,如基本數(shù)據(jù)類型對應(yīng)的Class對象瓶摆,一些常駐的異常對象(比如NullPointExcepiton、OutOfMemoryError)等缴渊,還有系統(tǒng)類加載器赏壹。
6、所有被同步鎖(synchronized關(guān)鍵字)持有的對象衔沼。
6蝌借、反映Java虛擬機(jī)內(nèi)部情況的JMXBean、JVMTI中注冊的回調(diào)指蚁、本地代碼緩存等菩佑。
7、根據(jù)用戶所選用的垃圾收集器以及當(dāng)前回收的內(nèi)存區(qū)域不 同凝化,“臨時性”地加入的其他對象稍坯。
在我們代碼中常見的GC Roots 主要包含:棧正在使用的對象,局部變量搓劫,靜態(tài)變量瞧哟。
java中的引用分為強(qiáng)引用,弱引用枪向,軟引用勤揩,虛引用。此次不在延申這些引用的用法秘蛔。對與垃圾回收來說陨亡,強(qiáng)引用 并且有GC Roots的對象不會被回收傍衡。其他引用不管有沒有GC Roots都會被回收。
未完待續(xù)~~