Java與C++等語言最大的技術區別:自動化的垃圾回收機制(GC)java
爲何要了解GC和內存分配策略面試
1、面試須要算法
2、GC對應用的性能是有影響的;緩存
3、寫代碼有好處服務器
棧:棧中的生命週期是跟隨線程,因此通常不須要關注多線程
堆:堆中的對象是垃圾回收的重點併發
方法區/元空間:這一塊也會發生垃圾回收,不過這塊的效率比較低,通常不是咱們關注的重點ide
給對象添加一個引用計數器,當對象增長一個引用時計數器加 1,引用失效時計數器減 1。引用計數爲 0 的對象可被回收。(Python在用,但主流虛擬機沒有使用)佈局
優勢:快,方便,實現簡單。性能
缺陷:對象相互引用時(A.instance=B同時B.instance=A),很難判斷對象是否該回收。
(面試時重要的知識點,牢記)
來斷定對象是否存活的。這個算法的基本思路就是經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。
做爲GC Roots的對象包括下面幾種:
當前虛擬機棧中局部變量表中的引用的對象
當前本地方法棧中局部變量表中的引用的對象
方法區中類靜態屬性引用的對象
方法區中的常量引用的對象
finalize能夠完成對象的拯救,可是JVM不保證必定能執行,因此請忘記這個「坑」。
傳統定義:Reference中存儲的數據表明的是另外一塊內存的起始地址。
通常的Object obj = new Object() ,就屬於強引用。
(若是有GCroots的強引用)垃圾回收器絕對不會回收它,當內存不足時寧願拋出 OOM 錯誤,使得程序異常中止
垃圾回收器在內存充足的時候不會回收它,而在內存不足時會回收它
軟引用很是適合於建立緩存。當系統內存不足的時候,緩存中的內容是能夠被釋放的。
一些有用可是並不是必需,用軟引用關聯的對象,系統將要發生OOM以前,這些對象就會被回收。參見代碼:
VM參數 -Xms10m -Xmx10m -XX:+PrintGC
運行結果
例如,一個程序用來處理用戶提供的圖片。若是將全部圖片讀入內存,這樣雖然能夠很快的打開圖片,但內存空間使用巨大,一些使用較少的圖片浪費內存空間,須要手動從內存中移除。若是每次打開圖片都從磁盤文件中讀取到內存再顯示出來,雖然內存佔用較少,但一些常用的圖片每次打開都要訪問磁盤,代價巨大。這個時候就能夠用軟引用構建緩存。
垃圾回收器在掃描到該對象時,不管內存充足與否,都會回收該對象的內存。
一些有用(程度比軟引用更低)可是並不是必需,用弱引用關聯的對象,只能生存到下一次垃圾回收以前,GC發生時,無論內存夠不夠,都會被回收。
參看代碼:
注意:軟引用 SoftReference和弱引用 WeakReference,能夠用在內存資源緊張的狀況下以及建立不是很重要的數據緩存。當系統內存不足的時候,緩存中的內容是能夠被釋放的。
實際運用(WeakHashMap、ThreadLocal)
幽靈引用,最弱,被垃圾回收的時候收到一個通知
若是一個對象只具備虛引用,那麼它和沒有任何引用同樣,任什麼時候候均可能被回收。
虛引用主要用來跟蹤對象被垃圾回收器回收的活動
案例Oom類
-Xms 堆區內存初始內存分配的大小
-Xmx 堆區內存可被分配的最大上限
-XX:+PrintGCDetails
打印GC詳情
-XX:+HeapDumpOnOutOfMemoryError
當堆內存空間溢出時輸出堆的內存快照
新生代大小配置參數的優先級:
中間 -Xmn 限定大小
-XX:SurvivorRatio
2個Survivor區和Eden區的比值
8 表示 兩個Survivor : Eden = 2: 8 ,每一個Survivor佔 1/10
能夠修改成2
8 表示 兩個Survivor : Eden = 2: 2 ,各佔一半
GC overhead limit exceeded 超過98%的時間用來作GC而且回收了不到2%的堆內存時會拋出此異常
1.垃圾回收會佔據資源
2.回收效率太低也會有限制
爲何new出的對象不會被回收了,咱們來看看GC是如何判斷對象的存活
特色: 發生在新生代上,發生的較頻繁,執行速度較快
觸發條件: Eden區空間不足\空間分配擔保
特色:主要發生在老年代上(新生代也會回收),較少發生,執行速度較慢
觸發條件:
調用 System.gc()
老年代區域空間不足
空間分配擔保失敗
JDK 1.7 及之前的永久代(方法區)空間不足
CMS GC處理浮動垃圾時,若是新生代空間不足,則採用空間分配擔保機制,若是老年代空間不足,則觸發Full GC
將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜狀況,只要按順序分配內存便可,實現簡單,運行高效。只是這種算法的代價是將內存縮小爲了原來的一半。
注意:內存移動是必須實打實的移動(複製),不能使用指針玩。
專門研究代表,新生代中的對象98%是「朝生夕死」的,因此通常來講回收佔據10%的空間夠用了,因此並不須要按照1:1的比例來劃份內存空間,而是將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor[1]。當回收時,將Eden和Survivor中還存活着的對象一次性地複製到另一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor空間。
HotSpot虛擬機默認Eden和Survivor的大小比例是8:1,也就是每次新生代中可用內存空間爲整個新生代容量的90%(80%+10%),只有10%的內存會被「浪費」。
過程:
缺點:
1.效率問題,標記和清除效率都不高
2.標記清除以後會產生大量不連續的內存碎片,空間碎片太多可能會致使之後在程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。
首先標記出全部須要回收的對象,在標記完成後,後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
根據各個年代的特色選取不一樣的垃圾收集算法
新生代使用複製算法
老年代使用標記-整理或者標記-清除算法
jps -v顯示當前使用的垃圾回收器
在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集。
而老年代中由於對象存活率高、沒有額外空間對它進行分配擔保,就必須使用「標記—清理」或者「標記—整理」算法來進行回收。
請記住下圖的垃圾收集器和之間的連線關係。
並行:垃圾收集的多線程的同時進行。
併發:垃圾收集的多線程和應用的多線程同時進行。
注:吞吐量=運行用戶代碼時間/(運行用戶代碼時間+ 垃圾收集時間)
垃圾收集時間= 垃圾回收頻率 * 單次垃圾回收時間
最古老的,單線程,獨佔式,成熟,適合單CPU 服務器
-XX:+UseSerialGC 新生代和老年代都用串行收集器
-XX:+UseParNewGC 新生代使用ParNew,老年代使用Serial Old
-XX:+UseParallelGC 新生代使用ParallerGC,老年代使用Serial Old
和Serial基本沒區別,惟一的區別:多線程,多CPU的,停頓時間比Serial少
-XX:+UseParNewGC 新生代使用ParNew,老年代使用Serial Old
除了性能緣由外,主要是由於除了 Serial 收集器,只有它能與 CMS 收集器配合工做。
關注吞吐量的垃圾收集器,高吞吐量則能夠高效率地利用CPU時間,儘快完成程序的運算任務,主要適合在後臺運算而不須要太多交互的任務。
所謂吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。
收集器是一種以獲取最短回收停頓時間爲目標的收集器。目前很大一部分的Java應用集中在互聯網站或者B/S系統的服務端上,這類應用尤爲重視服務的響應速度,但願系統停頓時間最短,以給用戶帶來較好的體驗。CMS收集器就很是符合這類應用的需求。
-XX:+UseConcMarkSweepGC ,通常新生代使用ParNew,老年代的用CMS
從名字(包含「Mark Sweep」)上就能夠看出,CMS收集器是基於「標記—清除」算法實現的,它的運做過程相對於前面幾種收集器來講更復雜一些,
整個過程分爲4個步驟,包括:
l 初始標記:僅僅只是標記一下 GC Roots 能直接關聯到的對象,速度很快,須要停頓(STW -Stop the world)。
l 併發標記:從GC Root 開始對堆中對象進行可達性分析,找到存活對象,它在整個回收過程當中耗時最長,不須要停頓。
l 從新標記:爲了修正併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄,須要停頓(STW)。這個階段的停頓時間通常會比初始標記階段稍長一些,但遠比並發標記的時間短。
l 併發清除:不須要停頓。
因爲整個過程當中耗時最長的併發標記和併發清除過程收集器線程均可以與用戶線程一塊兒工做,因此,從整體上來講,CMS收集器的內存回收過程是與用戶線程一塊兒併發執行的。
CPU資源敏感:由於併發階段多線程佔據CPU資源,若是CPU資源不足,效率會明顯下降。
浮動垃圾:因爲CMS併發清理階段用戶線程還在運行着,伴隨程序運行天然就還會有新的垃圾不斷產生,這一部分垃圾出如今標記過程以後,CMS沒法在當次收集中處理掉它們,只好留待下一次GC時再清理掉。這一部分垃圾就稱爲「浮動垃圾」。
因爲浮動垃圾的存在,所以須要預留出一部份內存,意味着 CMS 收集不能像其它收集器那樣等待老年代快滿的時候再回收。
在1.6的版本中老年代空間使用率閾值(92%)
若是預留的內存不夠存放浮動垃圾,就會出現 Concurrent Mode Failure,這時虛擬機將臨時啓用 Serial Old 來替代 CMS。
會產生空間碎片:標記 - 清除算法會致使產生不連續的空間碎片
G1中重要的參數:
-XX:+UseG1GC 使用G1垃圾回收器
G1 把堆劃分紅多個大小相等的獨立區域(Region),新生代和老年代再也不物理隔離。
算法:標記—整理 (humongous) 和複製回收算法(survivor)。
選定全部年輕代裏的Region。經過控制年輕代的region個數,即年輕代內存大小,來控制young GC的時間開銷。(複製回收算法)
選定全部年輕代裏的Region,外加根據global concurrent marking統計得出收集收益高的若干老年代Region。在用戶指定的開銷目標範圍內儘量選擇收益高的老年代Region。
Mixed GC不是full GC,它只能回收部分老年代的Region。若是mixed GC實在沒法跟上程序分配內存的速度,致使老年代填滿沒法繼續進行Mixed GC,就會使用serial old GC(full GC)來收集整個GC heap。因此咱們能夠知道,G1是不提供full GC的。
初始標記:僅僅只是標記一下GC Roots 能直接關聯到的對象,而且修改TAMS(Nest Top Mark Start)的值,讓下一階段用戶程序併發運行時,能在正確能夠的Region中建立對象,此階段須要停頓線程(STW),但耗時很短。
併發標記:從GC Root 開始對堆中對象進行可達性分析,找到存活對象,此階段耗時較長,但可與用戶程序併發執行。
最終標記:爲了修正在併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分標記記錄,虛擬機將這段時間對象變化記錄在線程的 Remembered Set Logs 裏面,最終標記階段須要把 Remembered Set Logs 的數據合併到 Remembered Set 中。這階段須要停頓線程(STW),可是可並行執行。
篩選回收:首先對各個 Region 中的回收價值和成本進行排序,根據用戶所指望的 GC 停頓時間來制定回收計劃。此階段其實也能夠作到與用戶程序一塊兒併發執行,可是由於只回收一部分 Region,時間是用戶可控制的,並且停頓用戶線程將大幅度提升收集效率。
空間整合:不會產生內存碎片
算法:標記—整理 (humongous) 和複製回收算法(survivor)。
可預測的停頓:
G1收集器之因此能創建可預測的停頓時間模型,是由於它能夠有計劃地避免在整個Java堆中進行全區域的垃圾收集。G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所得到的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的Region(這也就是Garbage-First名稱的來由)。這種使用Region劃份內存空間以及有優先級的區域回收方式,保證了G1收集器在有限的時間內能夠獲取儘量高的收集效率。
G1把內存「化整爲零」的思路,理解起來似
G1 GC主要的參數
參數 |
含義 |
-XX:G1HeapRegionSize=n |
設置Region大小,並不是最終值 |
-XX:MaxGCPauseMillis |
設置G1收集過程目標時間,默認值200ms,不是硬性條件 |
-XX:G1NewSizePercent |
新生代最小值,默認值5% |
-XX:G1MaxNewSizePercent |
新生代最大值,默認值60% |
-XX:ParallelGCThreads |
STW期間,並行GC線程數 |
-XX:ConcGCThreads=n |
併發標記階段,並行執行的線程數 |
-XX:InitiatingHeapOccupancyPercent |
設置觸發標記週期的 Java 堆佔用率閾值。默認值是45%。這裏的java堆佔比指的是non_young_capacity_bytes,包括old+humongous |
參數 |
描述 |
UseSerialGC |
虛擬機運行在Client模式下的默認值,打開此開關後,使用 Serial+Serial Old 的收集器組合進行內存回收 |
UseParNewGC |
打開此開關後,使用 ParNew + Serial Old 的收集器組合進行內存回收 |
UseConcMarkSweepGC |
打開此開關後,使用 ParNew + CMS + Serial Old 的收集器組合進行內存回收。Serial Old 收集器將做爲 CMS 收集器出現 Concurrent Mode Failure 失敗後的後備收集器使用 |
UseParallelGC |
虛擬機運行在 Server 模式下的默認值,打開此開關後,使用 Parallel Scavenge + Serial Old(PS MarkSweep) 的收集器組合進行內存回收 |
UseParallelOldGC |
打開此開關後,使用 Parallel Scavenge + Parallel Old 的收集器組合進行內存回收 |
SurvivorRatio |
新生代中 Eden 區域與 Survivor 區域的容量比值,默認爲8,表明 Eden : Survivor = 8 : 1 |
PretenureSizeThreshold |
直接晉升到老年代的對象大小,設置這個參數後,大於這個參數的對象將直接在老年代分配 |
MaxTenuringThreshold |
晉升到老年代的對象年齡,每一個對象在堅持過一次 Minor GC 以後,年齡就增長1,當超過這個參數值時就進入老年代 |
UseAdaptiveSizePolicy |
動態調整 Java 堆中各個區域的大小以及進入老年代的年齡 |
HandlePromotionFailure |
是否容許分配擔保失敗,即老年代的剩餘空間不足以應付新生代的整個 Eden 和 Survivor 區的全部對象都存活的極端狀況 |
ParallelGCThreads |
設置並行GC時進行內存回收的線程數 |
GCTimeRatio |
GC 時間佔總時間的比率,默認值爲99,即容許 1% 的GC時間,僅在使用 Parallel Scavenge 收集器生效 |
MaxGCPauseMillis |
設置 GC 的最大停頓時間,僅在使用 Parallel Scavenge 收集器時生效 |
CMSInitiatingOccupancyFraction |
設置 CMS 收集器在老年代空間被使用多少後觸發垃圾收集,默認值爲 68%,僅在使用 CMS 收集器時生效 |
UseCMSCompactAtFullCollection |
設置 CMS 收集器在完成垃圾收集後是否要進行一次內存碎片整理,僅在使用 CMS 收集器時生效 |
CMSFullGCsBeforeCompaction |
設置 CMS 收集器在進行若干次垃圾收集後再啓動一次內存碎片整理,僅在使用 CMS 收集器時生效 |
GC收集器和咱們GC調優的目標就是儘量的減小STW的時間和次數。