之前老大讓做一些外包面試,我的問題很簡單:
第一個問題主要是想了解一下對方項目經驗的含金量,第二個問題則是測試下是否知道一些細節,比如HashMap是線程不安全的、用HashMap來做緩存的話可能導致內存洩露等,自我感覺問題設計的還可以:D~ 但是看了其他同事的題目就淚崩了:
擦,怎麼感覺這個問題我也不會。。。
虛擬機給人的感覺像是操作系統、編譯器:非常高大上。但是Java程序就跑在上面,遇到問題還得去排查,性能不行還得去優化,基礎的知識還是需要的!
Java虛擬機在執行的過程中會把它所管理的內存劃分為若干個不同的數據區域,大致如下:
各部分的功能如下:
在內存管理部分比較大的一塊內容是GC(垃圾回收),所謂垃圾回收就是將垃圾占用的內存回收掉。那麼第一個問題:什麼是垃圾?
這裡都提到了引用,在JDK 1.2之後Java就已經對引用的概念進行了擴充,那麼第二個問題:有哪些類型的引用?
這裡只有強引用才能對對象的生命周期造成影響。在虛擬機發展的過程中進化出不少垃圾回收算法,比如:
在實際中用到的回收器都是這幾種算法的組合,比如從VisualVM中看到的內存是這樣的(需要明白各部分都是怎樣互相配合的):
整體上來看是分代收集算法,而S0、S1這兩部分可以看做是標記-整理算法。那麼第三個問題:常見的CMS垃圾回收器的執行流程是怎樣的?
具體如下圖所示:
可以看到只有在初始標記和重新標記的時候才需要Stop The World,其他都是和用戶線程一起執行,不要以為這就完美了,並行執行的過程會消耗掉一些CPU資源。
把Java源碼丟給JVM肯定是不能執行的,需要先用javac編譯成class文件才行,那麼第一個問題:class文件的結構是怎樣的?
虛擬機規范並沒有規定在什麼時候要加載類,但是規定了在遇到new、反射、父類、Main的時候需要初始化完成。整個類的生命周期如下:
在虛擬機中通過ClassLoader來進行類的加載,這地方需要明白:
在類加載完成之後就可以開始執行了,和線程運轉相關的東西都放在棧幀中,其結構如下:
執行中具體調用哪個方法是個頭疼的問題,需要處理:
字節碼中的指令都是基於棧的操作,比如要完成1+1這樣的計算,對應的指令如下:
iconst_1 // 將常量1壓入棧 iconst_1 iadd // 把棧頂的兩個值相加並出棧,然後把結果放回棧 istore_0 // 將棧頂的值放到局部變量表第0個Solt
解釋執行的好處是下載後啟動速度快,但是確定也非常明顯:運行速度慢。JIT正是用來解決這個問題的,能夠將多次調用的方法、多次執行的循環體編譯成本地代碼。
優化是個很好玩的題目,記得在參加一次變成比賽的時候用gcc -O3編譯之後的代碼把printf()都沒輸出了。。在JIT中比較常見的優化手段有:
程序執行一定會涉及到內存操作,在Java中定義了八種操作來完成:
這裡有必要講一下volatile的作用,在使用到的時候能明白下面兩條即可:
如果Java中所有的操作都需要程序員來控制的話,會有大量的重復代碼,而且寫起來很累,那麼我們可以通過先行發生原則來判斷並行的兩個操作是否存在沖突:
Thread的底層實現還是比較麻煩的,但是最起碼應該知道Thread的狀態是如何進行轉換:
最後,常見的同步方式是synchronized或者aqs的各種實現,這裡就不講了,因為每個都足夠寫一大篇。