引言:JVM常見面試題:
1艾船、請談?wù)勀銓VM的理解?java8的虛擬機(jī)有什么更新高每?
2屿岂、什么是OOM?什么是StackOverflowError鲸匿?有哪些方法分析爷怀?
3、JVM的常用調(diào)優(yōu)參數(shù)你知道哪些带欢?
4运授、談?wù)凧VM中,對類加載器你的認(rèn)識乔煞?
5吁朦、JVM內(nèi)存模型以及分區(qū),需要詳細(xì)到每個區(qū)放什么
6渡贾、堆里面的分區(qū):Eden逗宜、survival from to、老年代各自的特點
7空骚、GC的三種收集算法:標(biāo)記清除纺讲、標(biāo)記整理、復(fù)制算法的原理與特點囤屹,分別用在什么地方
8熬甚、Minor GC與Full GC分別在什么時候發(fā)生
一、類裝載器(ClassLoader):
負(fù)責(zé)加載class文件肋坚,class文件在文件開頭有特定的文件標(biāo)識则涯,將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中复局,并將這些內(nèi)容轉(zhuǎn)換成方法區(qū)中的運行時數(shù)據(jù)結(jié)構(gòu)并且ClassLoader只負(fù)責(zé)class文件的加載,至于它是否可以運行粟判,則由Execution Engine決定
啟動類加載器加載的是java程序最底層的一下jar包亿昏,放在rt.jar,java的版本在不斷的更迭档礁,新的jar包由擴(kuò)展類加載器進(jìn)行加載角钩。應(yīng)用程序類加載器則是加載我們在程序中自己寫的類。
public class Obb {
public static void main(String[] args) {
Object o = new Object();
System.out.println("o:" + o.getClass().getClassLoader());
Obb ob = new Obb();
System.out.println("ob:" + ob.getClass().getClassLoader());
}
}
如上所示一段代碼呻澜,Object是jdk自帶的递礼,所以它的類加載器應(yīng)該是根加載器,而ob是我自己定義的羹幸,他的加載器理所當(dāng)然的應(yīng)該是應(yīng)用程序類加載器脊髓,根加載器返回的是null。
二栅受、本地方法棧和本地方法接口
三将硝、程序計數(shù)器
四、方法區(qū)(Method Area):
供各線程共享時的內(nèi)存區(qū)域屏镊。它存儲了每一個類的結(jié)構(gòu)信息
依疼,例如運行時常量池、字段和方法數(shù)據(jù)而芥、構(gòu)造函數(shù)和普通方法的字節(jié)碼內(nèi)容律罢。上面講的時規(guī)范,在不同虛擬機(jī)里頭實現(xiàn)時不一樣的棍丐,最典型的就是永久代(java8以前)和元空間(java8以后)误辑。
實例變量時存放在堆內(nèi)存的,和方法區(qū)無關(guān)歌逢。
五巾钉、棧(Stack):
棧也叫棧內(nèi)存,主管java程序的運行趋翻,是在線程創(chuàng)建時創(chuàng)建,它的生命周期是跟隨線程的生命周期盒蟆,線程結(jié)束棧內(nèi)存也就釋放踏烙,對于棧來說不存在垃圾回收問題,只要線程一結(jié)束历等,該棧就Over讨惩,生命周期和線程一致,是線程私有的寒屯。8種基本類型的變量+對象的引用變量+實例方法都是在函數(shù)的棧內(nèi)存種分配荐捻。
棧管運行黍少,堆管存儲。
棧存儲什么处面?
棧幀種主要保存三類數(shù)據(jù):
本地變量:輸入?yún)?shù)和輸出參數(shù)以及方法內(nèi)的變量厂置;
棧操作:記錄出棧,入棧的操作魂角;
棧幀數(shù)據(jù):包括類文件昵济、方法等等。
/**
* 如下所示野揪,main方法作為程序的入口访忿,首先被作為棧幀壓入棧的底部,
* 然后main方法又調(diào)用了sayHello方法斯稳,將此方法棧幀壓入棧中海铆,
* 而sayHello方法又不停的自己調(diào)用,每次調(diào)用方法都會形成一個棧幀壓入棧挣惰,
* 而棧的大小是有限的卧斟,所以最后會導(dǎo)致棧溢出 StackOverflowError
*注意StackOverflowError是錯誤而不是異常
*/
public class StackDemo1 {
public static void main(String[] args) {
System.out.println("1");
sayHello();
System.out.println("3");
}
public static void sayHello() {
System.out.println("hello");
sayHello();
}
}
在棧中存著對象的引用唆涝,指向堆中,在棧中每一個引用存儲著的是對象的地址唇辨,都會在堆中對應(yīng)著一個類元數(shù)據(jù)地址廊酣,而堆中的類元數(shù)據(jù)地址會指向方法區(qū),因為方法區(qū)存儲著這個對象的結(jié)構(gòu)數(shù)據(jù)赏枚,也就是一個對象的模板亡驰。
六、堆(Heap):
一個JVM實例只存在一個堆內(nèi)存饿幅,堆內(nèi)存的大小是可以調(diào)節(jié)的凡辱,類加載器讀取了類文件后,需要把類栗恩、方法透乾、常變量放到堆內(nèi)存中,保存所有引用類型的真實信息磕秤,以方便執(zhí)行器執(zhí)行乳乌。堆內(nèi)存分為三部分:
PS:
幸存者0區(qū)又稱為S0區(qū)/From區(qū)
幸存者1區(qū)又稱為S1區(qū)/To區(qū)
java堆從GC的角度上還可以細(xì)分為:新生代(Eden區(qū)、From Survivor區(qū)和To Survivor區(qū))和老年代市咆。
元空間是一個常駐內(nèi)存區(qū)域汉操,用于存放JDK自身所攜帶的Class,Interface的元數(shù)據(jù)蒙兰,也就是說它存儲的是運行環(huán)境必須的類信息磷瘤,被裝載進(jìn)此區(qū)域的數(shù)據(jù)是不會被垃圾回收器回收掉的芒篷,關(guān)閉JVM才會釋放此區(qū)域所占用的內(nèi)存。
七采缚、JVM的參數(shù)調(diào)節(jié)
如上圖所見针炉,java7和java8對于jvm參數(shù)的調(diào)整,實際上相差不大仰担,
-Xms表示堆內(nèi)存的起始值糊识,默認(rèn)是物理內(nèi)存的1/64
-Xmx表示堆內(nèi)存的最大值,默認(rèn)是物理內(nèi)存的1/4
-Xmn表示堆中young區(qū)設(shè)置的值
默認(rèn)空余堆內(nèi)存小于40%時摔蓝,JVM就會增大堆直到-Xmx的最大限制赂苗;
空余堆內(nèi)存大于70%時,JVM會減少堆直到 -Xms的最小限制贮尉。
因此服務(wù)器一般設(shè)置-Xms拌滋、-Xmx相等
以避免在每次GC 后調(diào)整堆的大小。對象的堆內(nèi)存由稱為垃圾回收器的自動內(nèi)存管理系統(tǒng)回收猜谚。
(java8以前的永久代)
-XX:PermSize設(shè)置非堆內(nèi)存初始值败砂,默認(rèn)是物理內(nèi)存的1/64;
XX:MaxPermSize設(shè)置最大非堆內(nèi)存的大小魏铅,默認(rèn)是物理內(nèi)存的1/4昌犹。
在java8以后,永久代已經(jīng)被移除览芳,被元空間所代替斜姥,元空間本質(zhì)上和永久代類似。區(qū)別在于永久代使用的是JVM的堆內(nèi)存沧竟,但是java8以后元空間并不在虛擬機(jī)中而是使用本機(jī)物理內(nèi)存铸敏。因此,默認(rèn)情況下悟泵,元空間的大小受本地內(nèi)存限制杈笔。類的元數(shù)據(jù)放入native memory,字符串池和類的靜態(tài)變量放入java堆中糕非,這樣可以加載多少類的元數(shù)據(jù)就不再由maxPerSize控制蒙具,而是由系統(tǒng)的實際可用空間來控制。
public class jvmSwitch {
public static void main(String[] args) {
//返回JVM試圖使用的最大內(nèi)存量朽肥,默認(rèn)的是系統(tǒng)內(nèi)存的1/4左右 3616MB/16G
long maxMemory = Runtime.getRuntime().maxMemory();
//返回JVM的內(nèi)存總量
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("MAX_MEMORY=" + maxMemory + "(字節(jié))" +
(maxMemory / 1024 / 1024) + "MB");
System.out.println("TOTAL_MEMORY=" + totalMemory + "(字節(jié))" +
(totalMemory / 1024 / 1024) + "MB");
}
}
八:GC算法
分代收集算法=ぁ!鞠呈!
1融师、GC是分代收集算法:次數(shù)上頻繁收集的是Young區(qū)右钾、次數(shù)上較少收集的是old區(qū)蚁吝,基本不動的是元空間旱爆。
JVM在進(jìn)行GC時,并非每次都對三個內(nèi)存一起回收的窘茁,大部分時候回收的都是新生代怀伦,因此GC按照回收的區(qū)域又分了兩種類型,一種是普通GC(minor GC)山林,一種時全局GC(major GC or Full GC)
Minor GC和Full GC的區(qū)別:
minorGC:只針對新生代區(qū)域的GC房待,指發(fā)生在新生代的垃圾收集動作,因為大多數(shù)Java對象存活率都不高驼抹,所以Minor GC非常頻繁桑孩,一般回收速度也比較快。
majorGC:指發(fā)生在老年帶的垃圾收集動作框冀,出現(xiàn)了Major GC流椒,經(jīng)常會伴隨至少一次的Minor GC(但是不是絕對的),Major GC的速度一般要比Minor GC慢上10倍以上(因為老年代比新生代大懊饕病P骸)。
四大算法:引用計數(shù)法温数、復(fù)制算法绣硝、標(biāo)記清除法、標(biāo)記壓縮法
2撑刺、引用計數(shù)法
引用計數(shù)法如上所示鹉胖,在程序中有一個引用,就+1猜煮,但是可能會出現(xiàn)A引用B次员,B引用A的情況,這樣的話循環(huán)引用較難處理王带。
2淑蔚、復(fù)制算法
年輕代中的GC,主要靠的是復(fù)制算法愕撰,年輕代中分為一個Eden和兩個Survivor,默認(rèn)比例是8:1:1刹衫。一般情況下新創(chuàng)建的對象會被分配到Eden區(qū)(一些大的對象特殊處理)。因為年輕代中的對象基本上都是朝生夕死搞挣,所以年輕代的垃圾回收算法使用的是復(fù)制算法带迟,復(fù)制算法的思想就是將內(nèi)存分為兩塊,每次用其中一塊囱桨,當(dāng)這一塊內(nèi)存用完仓犬,就將還活著的對象復(fù)制到另一塊上面,復(fù)制算法不會產(chǎn)生內(nèi)存碎片舍肠。
在GC開始的時候,對象只會存在于Eden區(qū)和名為"From"的Survivor區(qū)财边,Survivor區(qū)"To"是空的酣难。緊接著進(jìn)行GC飞主,Eden區(qū)中所有存活的對象都會被復(fù)制到"To",而在“From”區(qū)中吵瞻,仍會存活的對象就會根據(jù)他們的年齡值來決定去向眯停。年齡達(dá)到一定值(年齡閾值莺债,可以通過-XX:MaxTenuringThreshold來設(shè)置)的對象會被移動到老年代签夭,沒有達(dá)到閾值的對象會被復(fù)制到"To"區(qū)域第租。經(jīng)過這次"From",新的"From"就是上次GC前的"To"丐吓,不管怎樣趟据,都會保證名為To的Survivor區(qū)域是空的汹碱。Minor GC會一直重復(fù)這樣的過程,直到“To”區(qū)域被填滿稚新,"To"區(qū)被填滿之后,會將所有對象移動到老年代中笤妙。
缺點:
1、浪費了一半的內(nèi)存
2召衔、如果對象存活率高苍凛,可以極端一點醇蝴,假設(shè)是100%存活想罕,那么我們需要將所有的對象都復(fù)制一遍悠栓,并將所有引用地址重置一遍按价,復(fù)制這一工作所花費的時間,在對象存活率達(dá)到一定程度時楼镐,將會變得不可忽視癞志。所以要使用復(fù)制算法框产,存活率必須非常低才行今阳,更重要的是,要克服50%內(nèi)存的浪費盾舌。
3蘸鲸、標(biāo)記清除算法
1膝舅、優(yōu)缺點
優(yōu)點:不需要額外的空間
缺點:兩次掃描仍稀,耗時嚴(yán)重;會產(chǎn)生內(nèi)存碎片。
當(dāng)程序運行時享幽,若可以使用的內(nèi)存被耗盡的時候铲掐,GC線程就會被觸發(fā)并將程序暫停,隨后將要回收的對象標(biāo)記一遍值桩,最終統(tǒng)一回收這些對象摆霉,完成標(biāo)記清理工作接下來便讓應(yīng)用程序恢復(fù)運行。
缺點解釋:首先奔坟,他的缺點就是效率比較低(遞歸與全堆對象遍歷)携栋,而且在進(jìn)行GC的時候,需要停止應(yīng)用程序咳秉,這會導(dǎo)致用戶體驗非常差勁刻两。其次,主要缺點則是這種方式清理出來的空間內(nèi)存事不連續(xù)的滴某,因為死亡對象都是隨機(jī)出現(xiàn)在內(nèi)存的各個角落的磅摹,現(xiàn)在把它們清除之后,內(nèi)存的布局就會亂七八糟霎奢。為了應(yīng)對這一點户誓,JVM必須維持一個內(nèi)存的空間列表,這又是一種開銷幕侠,而且在分配數(shù)組對象的時候帝美,尋找連續(xù)的內(nèi)存空間會不太好找。
4晤硕、標(biāo)記整理法/標(biāo)記壓縮法(全稱:標(biāo)記-清除-整理算法)
老年代一般是由標(biāo)記清除或者是標(biāo)記清除+標(biāo)記整理
優(yōu)缺點:
優(yōu)點:沒有內(nèi)存碎片
缺點:需要移動對象的成本(耗時長)
效率不高悼潭,不僅要標(biāo)記所有存活對象,還要整理所有存活對象的引用地址舞箍,從效率上來講舰褪,標(biāo)記/整理算法要低于復(fù)制算法。
5疏橄、總結(jié):
內(nèi)存效率:復(fù)制算法>標(biāo)記清除算法>標(biāo)記整理算法(此處的效率只是簡單的對比時間復(fù)雜度占拍,實際情況不一定如此)
內(nèi)存整齊度:復(fù)制算法=標(biāo)記整理算法>標(biāo)記清除算法略就。
內(nèi)存利用率:標(biāo)記整理算法=標(biāo)記清除算法>復(fù)制算法
可以看出效率上來講,復(fù)制算法是最快的晃酒,但是浪費了太多的內(nèi)存表牢,而為了盡量兼顧上面所提到的三個指標(biāo),標(biāo)記/整理算法相對來說更平滑一些贝次,但效率上依然不盡如人意崔兴,它比復(fù)制算法多了一個標(biāo)記的階段,又比標(biāo)記/清除多了一個整理內(nèi)存的過程
沒有最優(yōu)的算法蛔翅,只有最合適的算法敲茄。======>>>>分代收集算法
年輕代
年輕代的特點是區(qū)域相對老年代較小,對象存活率低搁宾。這種情況用復(fù)制算法回收整理,速度是最快的倔幼。
老年代
這種情況存在大量存活率高的對象盖腿,復(fù)制算法變得不合適,一般是由標(biāo)記清除與標(biāo)記整理的混合實現(xiàn)损同。