寫在前面
之前老大讓做一些外包面試籍嘹,我的問題很簡單:
- 介紹一下工作中解決過比較有意思的問題慈鸠。
- HashMap使用中需要注意的點(diǎn)忿墅。
第一個問題主要是想了解一下對方項(xiàng)目經(jīng)驗(yàn)的含金量,第二個問題則是測試下是否知道一些細(xì)節(jié),比如HashMap是線程不安全的斥黑、用HashMap來做緩存的話可能導(dǎo)致內(nèi)存泄露等弟胀,自我感覺問題設(shè)計(jì)的還可以:D~ 但是看了其他同事的題目就淚崩了:
- 設(shè)計(jì)模式XXX
- 垃圾回收XXX
擦楷力,怎么感覺這個問題我也不會。孵户。萧朝。
虛擬機(jī)給人的感覺像是操作系統(tǒng)、編譯器:非常高大上夏哭。但是Java程序就跑在上面检柬,遇到問題還得去排查,性能不行還得去優(yōu)化竖配,基礎(chǔ)的知識還是需要的何址!
內(nèi)存管理
Java虛擬機(jī)在執(zhí)行的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域,大致如下:
各部分的功能如下:
在內(nèi)存管理部分比較大的一塊內(nèi)容是GC(垃圾回收)进胯,所謂垃圾回收就是將垃圾占用的內(nèi)存回收掉用爪。那么第一個問題:什么是垃圾?
- 引用計(jì)數(shù)算法:被引用次數(shù)為0的對象胁镐。
- 根搜索算法:從GC Roots沿著引用找不到的對象偎血。
這里都提到了引用,在JDK 1.2之后Java就已經(jīng)對引用的概念進(jìn)行了擴(kuò)充盯漂,那么第二個問題:有哪些類型的引用烁巫?
- 強(qiáng)引用:
Object o = new Object()
這種都是強(qiáng)引用。 - 軟引用:還有用但非必須的宠能,在OOM之前被回收亚隙。
- 弱引用:更弱的引用,在下次GC的時(shí)候被回收违崇。
- 虛引用:最弱的阿弃,唯一的作用是在對象被回收的時(shí)候可以收到通知诊霹。
這里只有強(qiáng)引用才能對對象的生命周期造成影響。在虛擬機(jī)發(fā)展的過程中進(jìn)化出不少垃圾回收算法渣淳,比如:
- 標(biāo)記-清除算法
- 復(fù)制算法
- 標(biāo)記-整理算法
- 分代收集算法
在實(shí)際中用到的回收器都是這幾種算法的組合脾还,比如從VisualVM中看到的內(nèi)存是這樣的(需要明白各部分都是怎樣互相配合的):
整體上來看是分代收集算法,而S0入愧、S1這兩部分可以看做是標(biāo)記-整理算法鄙漏。那么第三個問題:常見的CMS垃圾回收器的執(zhí)行流程是怎樣的?
- 初始標(biāo)記:GC Roots直接關(guān)聯(lián)的對象棺蛛。
- 并發(fā)標(biāo)記:Root Tracing怔蚌。
- 重新標(biāo)記:修復(fù)由于程序運(yùn)行導(dǎo)致標(biāo)記產(chǎn)生變動。
- 并發(fā)清除
具體如下圖所示:
可以看到只有在初始標(biāo)記和重新標(biāo)記的時(shí)候才需要Stop The World旁赊,其他都是和用戶線程一起執(zhí)行桦踊,不要以為這就完美了,并行執(zhí)行的過程會消耗掉一些CPU資源终畅。
代碼執(zhí)行
把Java源碼丟給JVM肯定是不能執(zhí)行的籍胯,需要先用javac編譯成class文件才行,那么第一個問題:class文件的結(jié)構(gòu)是怎樣的离福?
- 常量池
- 訪問標(biāo)志
- 類索引杖狼、父類索引和接口索引
- 字段表
- 方法表
- 屬性表
虛擬機(jī)規(guī)范并沒有規(guī)定在什么時(shí)候要加載類,但是規(guī)定了在遇到new妖爷、反射本刽、父類、Main的時(shí)候需要初始化完成赠涮。整個類的生命周期如下:
在虛擬機(jī)中通過ClassLoader來進(jìn)行類的加載,這地方需要明白:
- 兩個類是否相同暗挑,除了類名外還需要判斷ClassLoader是否相同笋除。
- 雙親委派模式并不是一個強(qiáng)制約束。
在類加載完成之后就可以開始執(zhí)行了炸裆,和線程運(yùn)轉(zhuǎn)相關(guān)的東西都放在棧幀中垃它,其結(jié)構(gòu)如下:
執(zhí)行中具體調(diào)用哪個方法是個頭疼的問題,需要處理:
- 靜態(tài)分派:相同名稱烹看、不同參數(shù)類型的方法国拇。
- 動態(tài)分派:繼承中復(fù)寫的方法。
字節(jié)碼中的指令都是基于棧的操作惯殊,比如要完成1+1這樣的計(jì)算酱吝,對應(yīng)的指令如下:
iconst_1 // 將常量1壓入棧
iconst_1
iadd // 把棧頂?shù)膬蓚€值相加并出棧,然后把結(jié)果放回棧
istore_0 // 將棧頂?shù)闹捣诺骄植孔兞勘淼?個Solt
解釋執(zhí)行的好處是下載后啟動速度快土思,但是確定也非常明顯:運(yùn)行速度慢务热。JIT正是用來解決這個問題的忆嗜,能夠?qū)?strong>多次調(diào)用的方法、多次執(zhí)行的循環(huán)體編譯成本地代碼崎岂。
優(yōu)化是個很好玩的題目捆毫,記得在參加一次變成比賽的時(shí)候用gcc -O3編譯之后的代碼把printf()
都沒輸出了。冲甘。在JIT中比較常見的優(yōu)化手段有:
程序執(zhí)行一定會涉及到內(nèi)存操作绩卤,在Java中定義了八種操作來完成:
這里有必要講一下volatile的作用,在使用到的時(shí)候能明白下面兩條即可:
- 保證變量對所有線程是可見的江醇。
- 禁止指令重排優(yōu)化濒憋。
如果Java中所有的操作都需要程序員來控制的話,會有大量的重復(fù)代碼嫁审,而且寫起來很累跋炕,那么我們可以通過先行發(fā)生原則來判斷并行的兩個操作是否存在沖突:
- 程序次序規(guī)則:單線程內(nèi)按照程序書寫順序。
- 管程鎖定規(guī)則:unlock必須在lock之前律适。
- volatile變量規(guī)則:寫操作先行發(fā)生于讀操作辐烂。
- 線程啟動規(guī)則:
Thread.start()
先于線程的其他任意方法。 - 線程終止規(guī)則:線程中所有的操作都先于對此線程的終止檢測捂贿。
- 線程中斷規(guī)則:
interrupt()
先于中斷檢測纠修。 - 對象終結(jié)規(guī)則:對象的初始化完成先于它的
finalize()
方法。 - 傳遞規(guī)則:如果A先于B厂僧、B先于C扣草,那么A先于C。
Thread的底層實(shí)現(xiàn)還是比較麻煩的颜屠,但是最起碼應(yīng)該知道Thread的狀態(tài)是如何進(jìn)行轉(zhuǎn)換:
最后辰妙,常見的同步方式是synchronized或者aqs的各種實(shí)現(xiàn),這里就不講了甫窟,因?yàn)槊總€都足夠?qū)懸淮笃?/p>