java垃圾回收机制串行_Java垃圾回收机制
Java語言是一門自動內存管理的語言,不再需要的對象可以通過垃圾回收自動進行內存釋放。
Java運行時內存區域劃分
JVM將Java程序運行時內存區域劃分成以下幾個部分:
程序計數器(Program Counter Register, PC)
java虛擬機棧
本地方法棧
java堆
方法區,方法區中包括運行時常量池
java運行時內存區域劃分.png
程序計數器可以看做是當前線程所執行字節碼的行號指示器。JVM依靠程序計數器來選取需要執行的下一條字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來實現。
虛擬機棧描述了Java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每個方法從調用開始至結束的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。
本地方法棧與java虛擬機棧類似,只不過它用于為虛擬機使用的Native方法服務。
Java堆用于存放對象實例數據,幾乎所有的對象實例都在這里分配內存。Java堆是垃圾收集器管理的主要區域。
方法區用于存儲已經被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
其中程序計數器、Java虛擬機棧和本地方法棧都是線程私有的,而java堆和方法區是線程所共享的。
Java垃圾回收主要作用于Java堆。而Java堆又劃分為年輕代,老年代。年輕代又分為Eden,Survivor(分為From Survivor和To Survivor)。Java中垃圾回收是分帶收集的,不同區域的回收算法是不同的。
判定對象是否已死
判定一個對象是否已死的方法主要有兩種:引用計數法和可達性分析算法(也叫根結點枚舉法)。
引用計數法
引用計數法思想很簡單:為每個對象維持一個引用計數器,當有一個地方引用它時,計數器加1;當引用失效時,計數器減1;任何時刻計數器為0的對象就是可以被回收的對象。
引用計數法優缺點
引用計數法的優點非常明顯:實現簡單,效率高。但是它無法解決循環引用問題:例如對象A和B互相引用,但是其他任何對象都沒有引用A和B或者被A和B引用,此時引用計數法判定A和B不可回收,但是事實上A和B都可以被回收。為了解決循環引用問題,可達性分析算法應運而生。
可達性分析算法
可達性分析算法思想是通過一個"GC Roots"集來判定對象是否已死,以"GC Roots"為起始點,從這些節點往下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,這個對象就是不可用的。示意圖如下:
可達性分析算法示意圖
在Java中,可作為 GC Root的對象包括以下幾種:
虛擬機棧(棧幀中的局部變量表)中引用的對象;
方法區中類靜態屬性引用的對象;
方法區中常量引用的對象;
本地方法棧中Native方法引用的對象。
垃圾收集算法
標記清除算法
算法的執行過程與名字一樣,先標記所有需要回收的對象,在標記完成后統一回收所有被標記的對象。該算法有兩個問題:
標記和清除過程效率不高。主要由于垃圾收集器需要從GC Roots根對象中遍歷所有可達的對象,并給這些對象加上一個標記,表明此對象在清除的時候被跳過,然后在清除階段,垃圾收集器會從Java堆中從頭到尾進行遍歷,如果有對象沒有被打上標記,那么這個對象就會被清除。顯然遍歷的效率是很低的
會產生很多不連續的空間碎片,所以可能會導致程序運行過程中需要分配較大的對象的時候,無法找到足夠的內存而不得不提前出發一次垃圾回收。
標記清除算法
復制算法
復制算法是為了解決標記-清除算法的效率問題的,其思想如下:將可用內存的容量分為大小相等的兩塊,每次只使用其中的一塊,當這一塊內存使用完了,就把存活著的對象復制到另外一塊上面,然后再把已使用過的內存空間清理掉。
優點:每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
缺點:算法的代價是將內存縮小為了原來的一半,未免太高了一點。
復制算法
現在的商業虛擬機都采用這種收集算法來回收新生代,新生代中的對象98%是“朝生夕死”的,所以并不需要按照1∶1的比例來劃分內存空間,而是將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。
當回收時,將Eden和Survivor中還存活著的對象一次性地復制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8∶1,也就是每次新生代中可用內存空間為整個新生代容量的90%,只有10%的內存會被“浪費”。
當然,90%的對象可回收只是一般場景下的數據,我們沒有辦法保證每次回收都只有不多于10%的對象存活,當Survivor空間不夠用時(例如,存活的對象需要的空間大于剩余一塊Survivor的空間),需要依賴其他內存(這里指老年代)進行分配擔保(Handle Promotion)。
標記-整理算法
復制收集算法在對象存活率較高時就要進行較多的復制操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
與標記-清除算法過程一樣,只不過在標記后不是對未標記的內存區域進行清理,二是讓所有的存活對象都向一端移動,然后清理掉邊界外的內存。該方法主要用于老年代。
標記-整理算法
分代收集算法
目前商用虛擬機都使用“分代收集算法”,所謂分代就是根據對象的生命周期把內存分為幾塊,一般把Java堆中分為新生代和老年代,這樣就可以根據對象的“年齡”選擇合適的垃圾回收算法。
新生代:“朝生夕死”,存活率低,使用復制算法。
老年代:存活率較高,使用“標記-清除”算法或者“標記-整理”算法。
垃圾收集器
JVM有7種不同的垃圾收集器,它們是垃圾收集算法的具體實現。下圖展示了這7種作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,還有用于回收整個Java堆的G1收集器。不同收集器之間的連線表示它們可以搭配使用。
垃圾收集器
各個收集器的特點如下:
Serial收集器(復制算法): 新生代單線程收集器,標記和清理都是單線程,優點是簡單高效;
Serial Old收集器 (標記-整理算法): 老年代單線程收集器,Serial收集器的老年代版本;
ParNew收集器 (復制算法): 新生代收并行集器,實際上是Serial收集器的多線程版本,在多核CPU環境下有著比Serial更好的表現;
Parallel Scavenge收集器 (復制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用戶線程時間/(用戶線程時間+GC線程時間),高吞吐量可以高效率的利用CPU時間,盡快完成程序的運算任務,適合后臺應用等對交互相應要求不高的場景;
Parallel Old收集器 (標記-整理算法): 老年代并行收集器,吞吐量優先,Parallel Scavenge收集器的老年代版本;
CMS(Concurrent Mark Sweep)收集器(標記-清除算法): 老年代并行收集器,以獲取最短回收停頓時間為目標的收集器,具有高并發、低停頓的特點,追求最短GC回收停頓時間。
G1(Garbage First)收集器 (標記-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基于“標記-整理”算法實現,也就是說不會產生內存碎片。此外,G1收集器不同于之前的收集器的一個重要特點是:G1回收的范圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的范圍僅限于新生代或老年代。
收集器總結
收集器串行、并行or并發新生代or老年代算法目標適用場景
Serial
串行
新生代
復制算法
響應速度優先
單CPU環境下的Client模式
Serial Old
串行
老年代
標記-整理
響應速度優先
單CPU環境下的Client模式、CMS的后備預案
ParNew
并行
新生代
復制算法
響應速度優先
多CPU環境時在Server模式下與CMS配合
Parallel Scavenge
并行
新生代
復制算法
吞吐量優先
在后臺運算而不需要太多交互的任務
Parallel Old
并行
老年代
標記-整理
吞吐量優先
在后臺運算而不需要太多交互的任務
CMS
并發
老年代
標記-清除
響應速度優先
集中在互聯網站或B/S系統服務端上的Java應用
G1
并發
年輕代和老年代
標記-整理+復制算法
響應速度優先
面向服務端應用,將來替換CMS
內存分配與回收策略
Java技術體系中所提倡的自動內存管理最終可以歸結為自動化地解決了兩個問題:給對象分配內存 以及 回收分配給對象的內存。一般而言,對象主要分配在新生代的Eden區上,如果啟動了本地線程分配緩存(TLAB),將按線程優先在TLAB上分配。少數情況下也可能直接分配在老年代中。總的來說,內存分配規則并不是一層不變的,其細節取決于當前使用的是哪一種垃圾收集器組合,還有虛擬機中與內存相關的參數的設置。
對象優先在Eden分配,當Eden區沒有足夠空間進行分配時,虛擬機將發起一次MinorGC?,F在的商業虛擬機一般都采用復制算法來回收新生代,將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。 當進行垃圾回收時,將Eden和Survivor中還存活的對象一次性地復制到另外一塊Survivor空間上,最后處理掉Eden和剛才的Survivor空間。(HotSpot虛擬機默認Eden和Survivor的大小比例是8:1)當Survivor空間不夠用時,需要依賴老年代進行分配擔保。
大對象直接進入老年代。所謂的大對象是指,需要大量連續內存空間的Java對象,最典型的大對象就是那種很長的字符串以及數組。
長期存活的對象將進入老年代。當對象在新生代中經歷過一定次數(默認為15)的Minor GC后,就會被晉升到老年代中。
動態對象年齡判定。為了更好地適應不同程序的內存狀況,虛擬機并不是永遠地要求對象年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。
需要注意的是,Java的垃圾回收機制是Java虛擬機提供的能力,用于在空閑時間以不定時的方式動態回收無任何引用的對象占據的內存空間。也就是說,垃圾收集器回收的是無任何引用的對象占據的內存空間而不是對象本身。
Minor GC 和 Full GC
新生代GC(Minor GC):指發生在新生代的垃圾收集動作,因為Java對象大多都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快。
老年代GC(Major GC / Full GC):指發生在老年代的GC,出現了Major GC,經常會伴隨至少一次的Minor GC(但非絕對的,在Parallel Scavenge收集器的收集策略里就有直接進行Major GC的策略選擇過程)。Major GC的速度一般會比Minor GC慢10倍以上。
Minor GC和 Full GC觸發條件
Minor GC觸發條件:當Eden區滿時,觸發Minor GC。
Full GC觸發條件:
調用System.gc時,系統建議執行Full GC,但是不必然執行。
老年代空間不足。
方法去空間不足。
通過Minor GC后進入老年代的平均大小大于老年代的可用內存。
由Eden區、From Space區向To Space區復制時,對象大小大于To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小于該對象大小。
總結
以上是生活随笔為你收集整理的java垃圾回收机制串行_Java垃圾回收机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java if语句选择题_选择语句(if
- 下一篇: 用java解决国王的金币问题_国王赏赐金