深入理解JVM底層實現
模擬面試題QA
田超凡
20191116
- JVM常用的調優參數
-xmx 設置最大堆內存
-xms 設置最小堆內存
-xmn 設置新生代堆內存,默認大小是堆內存的1/3,新生代和老年代在堆區中的內存分配比例是1:2
-xss 設置線程棧大小
- JVM運行時數據區域有哪幾部分組成,各自作用
JVM運行時數據區主要有兩部分組成:
(1).線程共享區:所有線程共享的JVM內存資源,所有線程都可以訪問
線程共享區中有兩個區域:
堆區:存放創建的對象,管理對象的生命週期
- 新生代
存放的是生命週期短的對象,對象在新生代中轉瞬即逝
- Eden伊甸園區:新創建的對象默認優先放到Eden區
- Survivor 倖存者區:對象主要活動區域,使用複製算法控制對象年齡
- S0 區(From 區):複製算法作用起始區
- S1 區(To 區):複製算法作用目標區
- 老年代
存放的是新生代複製過來的長期存活的對象
方法區(元空間):存放運行時常量池(全局靜態常量)、Class類型的對象、全局靜態變量
(2).線程獨佔區:每個線程單獨佔有的JVM內存資源
虛擬機棧(Virtual Stack):存放線程運行時的局部變量
本地方法棧(Native Stack):存放的是JVM需要調用的本地native方法
程序計數器(Runner Counter):記錄當前線程執行到的行數
線程獨佔區的棧區主要是由棧楨組成的,每個線程每次方法調用都會在當前線程棧區中創建一個棧楨,每個棧楨主要有4部分組成:
- 局部變量表:存放的是方法中定義的局部變量
- 操作數棧:使用棧結構來存放局部變量(後進先出 Last In First Out)
- 動態鏈接:存放當前線程對方法區(元空間)中的對象引用,比如方法中引用到了靜態變量、靜態常量,此時就會把指針引用轉換成真實的引用。
- 返回地址:返回當前方法的執行結果
- GC算法有哪些?GC收集器有哪些?
GC垃圾回收算法有:
- .引用計數器算法
堆區中每個對象都使用一個計數器來記錄當前對象的年齡,沒執行一次複製算法,對象年齡都會+1,當達到默認分代年齡的時候,就會把對象從新生代複製到老年代
- .根搜索算法
把堆區中的對象分爲可達對象和不可達對象
首先定義GC Roots根節點,GC Roots根節點會使用引用鏈連接所有可達對象,根搜索算法就是每次從GC Roots根節點出發,回收不在根節點引用鏈上的不可達對象。
- .標記清除算法
標記清除算法分爲兩個階段:
對象標記:標記所有需要被垃圾回收的不可達對象(大多數說法是標記可達對象,但實際上這個說法不符合CMS垃圾回收器實現機制)
對象清除:清除所有上一步標記的需要被垃圾回收的對象
- .複製算法
- 當新生代Eden區執行YGC後,會把Eden區中存活的對象複製到Survivor S0區
- 當新生代Survivor S0區存滿之後,會把Eden區和Survivor S0區的倖存對象複製到Survivor S1區
- 當新生代Survivor S1區存滿之後,會把Survivor S1區中倖存的對象再次複製到Survivor S0區,依次類推,把對象在Survivor S0區和Survivor S1區之間來回複製。
- 當新生代中的對象達到分代大小或者分代年齡(默認分代年齡是15,可以通過JVM參數動態調整)之後,會把對象從新生代複製到老年代。
- .標記壓縮算法
在標記清除算法的基礎上來解決內存碎片問題,主要實現策略是對老年代中的對象活動區域進行動態壓縮,包括任意順序、線形順序、滑動順序
- .分代算法
根據對象的生命週期把對象活動區域分爲新生代和老年代
新生代存放生命週期短、臨時的對象
老年代存放生命週期長、長期存在的對象
分代算法可以有助於GC回收更加高效
GC垃圾回收器都是基於不同的GC垃圾回收算法實現的
新生代垃圾回收器都是基於複製算法實現的
老年代垃圾回收器都是基於標記清除算法和標記壓縮算法實現的
- 哪些可以作爲 GC Roots 的對象
- .虛擬機棧中的局部變量
- .方法區(元空間)中的全局靜態變量
- .方法區(元空間)中的全局靜態常量
- .本地方法棧中的native方法
- 你知道哪幾種垃圾收集器,各自的優缺點,重點講下cms
新生代常用的垃圾回收器有:
Serial 串行收集器,支持單線程垃圾回收,如果是多核服務器會導致效率降低
ParNew 並行收集器,支持多線程垃圾回收,如果是單核服務器會導致效率降低
ParallelScavenge 吞吐量優先收集器,吞吐量=用戶本地代碼執行時間/(用戶本地代碼執行時間+GC垃圾回收時間)
老年代常用的垃圾回收器有:
CMS 並行標記清除(Concurrent Mark Sweep)
支持多線程垃圾回收,主要基於根搜索算法+標記清除算法實現,垃圾回收需要經歷四個階段:
- .初始標記:在該階段會把老年代所有GC Roots直接連接的對象進行標記
- .併發標記:使用多線程並行的方式標記所有老年代GC Roots引用鏈上的可達對象
- .併發預處理:對併發標記中標記的新晉升到老年代的對象進行標記
- .重新標記:標記所有新生代引用的老年代的對象,需要重新掃描整個堆區
- .併發清除:使用多線程並行的方式對老年代沒有標記的對象進行垃圾回收
- .併發重置:重置清除完垃圾對象之後的老年代內存空間
注意:老年代CMS垃圾回收器只能和新生代Serial、ParNew垃圾回收器結合使用,不能和ParallelScavenge垃圾回收器結合使用。
HotSpot VM 默認使用的垃圾回收器都是並行收集器(新生代ParNew+老年代CMS)
Serial Old 串行垃圾回收,不常用
Parallel Older 吞吐量優先垃圾回收,不常用
- 什麼是Full GC?minor GC? major GC? STW?
Full GC指的是對整個堆區中的對象進行垃圾回收,等效於Minor gc+Major gc
Minor GC指的是對新生代中的對象進行垃圾回收,Minor gc會觸發STW
Major GC指的是對老年代中的對象進行垃圾回收,Major gc會觸發Full gc
STW指的是Stop The World,除當前垃圾回收線程外的其他線程都會暫停。
7.JVM中一次完整的GC流程(從YGC到FGC)是怎樣的,重點講講對象如何晉升到老年代
(1).新生代中的對象每執行一次複製算法進行YGC的時候,對象的年齡就會+1,當達到默認分代年齡的時候,就會把對象從新生代複製到老年代
(2).新生代中的大對象(對象大小超過分代大小閥值的對象)直接複製到老年代
(3).空間分配擔保:當新生代可用內存過小,Eden區無法存放創建的對象的時候,就會基於空間分配擔保機制執行YGC,把存活的對象直接複製到老年代,在老年代中單獨開闢一塊區域來存放新晉升到老年代的對象。
(4).動態對象年齡:當Survivor倖存者區相同年齡的對象數量>Survivor倖存者區所有對象總數的50%時,會把年齡大於該對象年齡的對象直接複製到老年代,不需要達到默認分代年齡
8.如何判斷是否有內存泄漏
內存泄漏表示堆區中的對象無法被GC回收,長期存活的對象超出了預期的GC回收閥值,可以通過jstack命令來查看JVM內存佔用情況來判斷是否出現了內存泄漏問題:
- .jstack pid > *.dump 可以把gc快照存放到dump文件中並使用jdk/bin/jvisualvm工具進行GC可視化分析
- .如果發現老年代對象在執行多次Full GC之後,老年代內存大小反而越來越大,則表示發生了內存泄漏,因爲內存泄漏時堆區中的對象是無法被GC回收的,也就導致堆區中的內存大小每次Full GC之後只會變得越來越大。
- .內存泄漏還有一個特徵點就是服務不可用,當發生內存泄漏時,所有服務都無法正常訪問和使用,必須清理內存空間後重新啓動服務
- OOM說一下?怎麼排查?哪些會導致OOM? OOM出現在什麼時候
OOM指的是OutOfMemory內存溢出異常,內存溢出是因爲內存泄漏導致的,因爲內存泄漏導致堆區中的對象無法正常被GC回收,達到一定數量之後堆區內存就會完全佔滿,此時再創建對象的時候就會發生內存溢出。
內存溢出是可以進行GC回收的,在發生內存溢出的時候需要使用Full GC強制垃圾回收整個堆空間中的垃圾對象來釋放出更多的堆區內存空間。使用jstack命令可以看到堆區的內存空間大小隨着Full GC的多次執行越來越小,則表示內存溢出問題已經被解決並釋放出了可用的內存空間來存放新創建的對象。
導致OOM內存溢出的問題有很多,比如死循環裝容器數據,後臺查詢沒用分頁(如果一次查詢出來萬條數據,每個數據映射的對象都比較大,那麼存到集合容器的時候就會把內存佔滿,導致內存溢出)
內存溢出是因爲內存泄漏導致的,因爲內存泄漏導致無法釋放對象資源導致堆區空間只會呈現出正增長的趨勢,堆區內存空間不能得到及時釋放,當堆區裝滿了之後還往堆區存放對象就會發生內存溢出OOM異常。
10.Java中都有哪些引用類型?
強引用:Object obj=new Object() 對象引用指向的是當前對象同類型本身,即對象類型符號和對象引用類型符號相同,強引用對象是不會被GC回收的
弱引用:每次GC垃圾回收都會回收掉的對象就是弱引用對象
軟引用:默認情況下不回收,但是當堆區空間佔滿的時候會回收的對象就是軟引用對象。
虛引用:Object obj=new Object(); obj=null; 傳遞一個空值給對象
提示JVM需要被GC垃圾回收的對象就是虛引用對象。
11.Java中的值類型有哪些?引用類型有哪些?
Java中的基本數據類型(byte/short/int/long/float/double/char/boolean)都是值類型,其他自定義類型和數組、集合類型都是引用類型。
改變值類型參數的值不會改變本身,改變的只是值的副本。
改變引用類型參數的值會直接改變原引用,因爲指向同一個堆區中的內存地址。
轉載請註明原作者