Java面試01|JVM相關
1、JVM內存查看與分析,編寫內存泄露實例
堆區、棧區、方法區、本機內存都有可能內存溢出。在這裏編寫堆區內存溢出實例。如下(來自《深入理解Java虛擬機》一書。
// -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError public class HeapOutOfMemoryError { static class OOMObject{} public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); while(true){ list.add(new OOMObject()); } } }
編寫溢出實例主要知道兩點內容:
(1)不斷創建實例對象,以占滿堆空間
(2)保證GC Roots到對象之間有可達路徑來避免垃圾回收
最後運行的結果如下截圖。
Dump出快照後查看泄露對象和泄露對象到GC Roots的引用鏈。安裝Eclipse Memory Analyzer到Eclipse IDE進行內存快照查看。
插件安裝地址:http://www.eclipse.org/mat/downloads.php
使用jstat命令來查看JVM內存的分配與使用情況,如下:
jstat -gc 24170(進程號) 1000(第1000ms采集一次數據) 2(采集兩次數據)
結果如下:
S0C:S0區容量(S1區相同) S0U:S0區已使用
EC: E區容量 EU:E區已使用
OC: 老年代容量 OU:老年代已使用
MC: 元數據容量 MU: 元數據區已使用
YGC: Young GC(Minor GC)次數 YGCT:Young GC總耗時 FGC: Full GC次數 FGCT:Full GC總耗時 GCT: GC總耗時
也可以使用Java VisualVM圖形界面進行查看。一般可以裝個Visual GC插件,如下所示。
介紹一下如上截圖的參數。
1、Compile Time(編譯時間):855compiles 表示編譯總數,6.606s表示編譯累計時間。一個脈沖表示一次JIT編譯,窄脈沖表示持續時間短,寬脈沖表示持續時間長。
2、Class Loader Time(類加載時間): 20869loaded表示加載類數量, 139 unloaded表示卸載的類數量,40.630s表示類加載花費的時間
3、GC Time(GC Time):2392collections表示垃圾收集的總次數,37.454s表示垃圾收集花費的時間,last cause表示最近垃圾收集的原因
4、Eden Space(Eden 區):括號內的31.500M表示最大容量,9.750M表示當前容量,後面的4.362M表示當前使用情況,2313collections表示垃圾收集次數,8.458s表示垃圾收集花費時間
5、Survivor 0/Survivor 1(S0和S1區):括號內的3.938M表示最大容量,1.188M表示當前容量,之後的值是當前使用情況
6、Old Gen(老年代):括號內的170.500M表示最大容量,67.500M表示當前容量,之後的31.825表示當前使用情況,3collections表示垃圾收集次數 ,450.789s表示垃圾收集花費時間
7、Perm Gen(永久代):括號內的96.000M表示最大容量,70.250M表示當前容量,之後的35.129M表示當前使用情況
2、對象的分配以及出發Minor GC與Full GC的條件
對象優先在Eden分配,當Eden區沒有足夠的空間進行分配時,虛擬機將發起一次Minor GC。
在發生Minor GC之前,JDK7規則是只要老年代的連續空間大於新生代對象總大小或者歷次晉升的平均大小,會發生Minor GC,否則會發生Full GC。
3、類加載過程,如何獲得當前對象的ClassLoader
類加載的過程分為:加載、連接、初始化、使用和卸載。其中連接又可以分為:驗證、準備和解析
獲取當前對象的ClassLoader通過如下代碼:
this.getClass().getClassLoader() // 獲取當前對象的類對象,然後調用getClassLoader
類加載器可以進行類層次的劃分、OSGi、熱部署和代碼加密等。那麽用戶如何自定義類加載器呢?
要創建用戶自己的類加載器,只需要擴展java.lang.ClassLoader類,然後覆蓋它的findClass(String name)方法即可,該方法根據參數指定類的名字,返回對應的Class對象的引用。
findClass protected Class<?> findClass(String name) throws ClassNotFoundException
使用指定的二進制名稱查找類。此方法應該被類加載器的實現重寫,該實現按照委托模型來加載類。在通過父類加載器檢查所請求的類後,此方法將被 loadClass 方法調用。默認實現拋出一個 ClassNotFoundException。
4、Java的先行發生關系 happens-before
JMM(Java內存模型)為所有程序內部動作定義了一個偏序關系,叫做happens-before。要想保證執行動作B的線程看到動作A的結果(無論A和B是否發生在同一個線程中),A和B之間就必須滿足happens-before關系。
參考《深入理解Java虛擬機》376頁
5、JVM的類載入器
(1)每個類加載器都加載哪些類
(2)如何自定義自己的類加載器,自己的類加載器和Java自帶的類加載器關系如何處理
不同類加載器的命名空間關系:
同一個命名空間內的類是相互可見的。
子加載器的命名空間包含所有父加載器的命名空間。因此子加載器加載的類能看見父加載器加載的類。例如系統類加載器加載的類能看見根類加載器加載的類。
由父加載器加載的類不能看見子加載器加載的類。
如果兩個加載器之間沒有直接或間接的父子關系,那麽它們各自加載的類相互不可見。
參考:
《Java程序員修煉之道》第110頁,看依賴註入中的類加載器
6、垃圾回收策略
具體說一下G1的回收策略:
Region劃分內存空間以及有優先級的區域回收方式,保證了G1收集器在有限的時間內可以獲取盡可能高的收集效率 。
7、JVM參數
參數大體可以分為:堆設置、收集器設置和垃圾回收統計信息
參數名稱 | 含義 | |
-Xms | 初始堆大小,默認為物理內存的1/64(<1GB) | 默認空余堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制. |
-Xmx | 最大堆大小,默認為物理內存的1/4(<1GB) | 默認空余堆內存大於70%時,JVM會減少堆直到 -Xms的最小限制 |
-Xmn | 年輕代大小(1.4 or lator) | 註意:此處的大小是(eden+ 2 survivor space).與jmap -heap中顯示的New gen是不同的。 整個堆大小=年輕代大小 + 年老代大小 + 持久代大小. 增大年輕代後,將會減小年老代大小.此值對系統性能影響較大,Sun官方推薦配置為整個堆的3/8 |
-XX:NewSize | 設置年輕代大小(for 1.3/1.4) | |
-XX:MaxNewSize | 年輕代最大值(for 1.3/1.4) | |
-Xss | 每個線程的堆棧大小 | 一般小的應用, 如果棧不是很深, 應該是128k夠用的 大的應用建議使用256k。這個選項對性能影響比較大,需要嚴格的測試。 |
-XX:NewRatio | 年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代) | -XX:NewRatio=4表示年輕代與年老代所占比值為1:4,年輕代占整個堆棧的1/5 Xms=Xmx並且設置了Xmn的情況下,該參數不需要進行設置。 |
-XX:SurvivorRatio | Eden區與Survivor區的大小比值 | 設置為8,則兩個Survivor區與一個Eden區的比值為2:8,一個Survivor區占整個年輕代的1/10 |
-XX:HeapDumpOnOutOfMemoryError | 發生OOM時轉儲堆到文件,這是一個非常好的診斷方法。 | |
-XX:+PrintGCDetails | 打印GC詳情 | |
-XX:+UseParallelGC | 設置並行收集器 |
Java面試01|JVM相關