java垃圾回收机制_JVM的垃圾回收机制——垃圾回收算法
一、Java垃圾回收機制
在java中,程序員是不需要顯示的去釋放一個對象的內存的,而是由虛擬機自行執行。在JVM中,有一個垃圾回收線程,它是低優先級的,在正常情況下是不會執行的,只有在虛擬機空閑或者當前堆內存不足時,才會觸發執行,掃描那些沒有被任何引用的對象,并將它們添加到要回收的集合中,進行回收。
二、GC是什么?為什么要GC
GC 垃圾收集(Gabage Collection),內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存。
不當的回收可能會導致程序或系統的不穩定甚至崩潰,Java 提供的 GC 功能可以自動監測對象是否超過作用域從而達到自動回收內存的目的,Java 語言沒有提供釋放已分配內存的顯示操作方法。
對于GC來說,當程序員創建對象時,GC就開始監控這個對象的地址、大小以及使用情況。
通常,GC采用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象是"可達的",哪些對象是"不可達的"。當GC確定一些對象為"不可達"時,GC就有責任回收這些內存空間。
程序員可以手動執行System.gc(),通知GC運行,但是Java語言規范并不保證GC一定會執行。
JVM GC三、Java 中的引用類型
四、如何判斷對象是否可以被回收?什么時候被回收?
垃圾收集器在做垃圾回收的時候,首先需要判定的就是哪些內存是需要被回收的,哪些對象是存活的,是不可以被回收的;哪些對象已經死亡了,需要被回收。
一般有兩種方法來判斷:
引用計數器法:為每個對象創建一個引用計數,有對象引用時計數器 +1,引用被釋放時計數 -1,當計數器為 0 時就可以被回收。但是他有一個缺點是不能解決循環引用的問題。
可達性分析算法:從 GC Roots 開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到 GC Roots 沒有任何引用鏈相連時,則證明此對象是可以被回收的。
當對象對當前使用這個對象的應用程序變得不可觸及的時候,這個對象就可以被回收了。
垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是為什么正確的永久代大小對避免Full GC是非常重要的原因。
五、JVM 的垃圾回收算法?
1.標記-清除算法:標記無用對象,然后進行清除回收。缺點:效率不高,無法清除垃圾碎片。
- 該算法分為兩個階段,標記和清除。標記階段標記所有需要回收的對象,清除階段回收被標記的對象所占用的空間。該算法最大的問題就是內存碎片嚴重化,后續可能發生對象不能找到利用空間的問題。
2.復制算法:按照容量劃分二個大小相等的內存區域,當一塊用完的時候將活著的對象復制到另一塊上,然后再把已使用的內存空間一次清理掉。缺點:內存使用率不高,只有原來的一半。
- 按內存容量將內存劃分為等大小的兩塊。每次只使用其中一塊,當這一塊內存滿后將尚存活的對象復制到另一塊上去,把已使用的內存清掉。
3.標記-整理算法:標記無用對象,讓所有存活的對象都向一端移動,然后直接清除掉端邊界以外的內存。
- 標記后不是清理對象,而是將存活對象移向內存的一端。然后清除端邊界外的對象。
4.分代算法:根據對象存活周期的不同將內存劃分為幾塊,一般是新生代和老年代,新生代基本采用復制算法,老年代采用標記整理算法。
- 當前商業虛擬機都采用分代收集的垃圾收集算法。分代收集算法,顧名思義是根據對象的存活周期將內存劃分為幾塊。一般包括年輕代、老年代 和 永久代。
六、垃圾收集器
Java 堆內存被劃分為新生代和年老代兩部分,新生代主要使用復制和標記-清除垃圾回收算法;
年老代主要使用標記-整理垃圾回收算法,因此 java 虛擬中針對新生代和年老代分別提供了多種不同的垃圾收集器,JDK1.6 中 Sun HotSpot 虛擬機的垃圾收集器如下:
垃圾收集器其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,
回收老年代的收集器包括Serial Old、Parallel Old、CMS,
有用于回收整個Java堆的G1收集器。
新生代垃圾回收器一般采用的是復制算法,復制算法的優點是效率高,缺點是內存利用率低;老年代回收器一般采用的是標記-整理的算法進行垃圾回收。
- Serial收集器(復制算法): 新生代單線程收集器,標記和清理都是單線程,優點是簡單高效;
- ParNew收集器 (復制算法): 新生代收并行集器,實際上是Serial收集器的多線程版本,在多核CPU環境下有著比Serial更好的表現;
- Parallel Scavenge收集器 (復制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用戶線程時間/(用戶線程時間+GC線程時間),高吞吐量可以高效率的利用CPU時間,盡快完成程序的運算任務,適合后臺應用等對交互相應要求不高的場景;
- Serial Old收集器 (標記-整理算法): 老年代單線程收集器,Serial收集器的老年代版本;
- Parallel Old收集器 (標記-整理算法): 老年代并行收集器,吞吐量優先,Parallel Scavenge收集器的老年代版本;
- CMS(Concurrent Mark Sweep)收集器(標記-清除算法): 老年代并行收集器,以獲取最短回收停頓時間為目標的收集器,具有高并發、低停頓的特點,追求最短GC回收停頓時間。
- G1(Garbage First)收集器 (標記-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基于“標記-整理”算法實現,也就是說不會產生內存碎片。此外,G1收集器不同于之前的收集器的一個重要特點是:G1回收的范圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的范圍僅限于新生代或老年代。
CMS 是英文 Concurrent Mark-Sweep 的簡稱,是以犧牲吞吐量為代價來獲得最短回收停頓時間的垃圾回收器。對于要求服務器響應速度的應用上,這種垃圾回收器非常適合。在啟動 JVM 的參數加上 “-XX:+UseConcMarkSweepGC” 來指定使用 CMS 垃圾回收器。
CMS 使用的是標記-清除的算法實現的,所以在 gc 的時候回產生大量的內存碎片,當剩余內存不能滿足程序運行要求時,系統將會出現 Concurrent Mode Failure,臨時 CMS 會采用 Serial Old 回收器進行垃圾清除,此時的性能將會被降低。
七、分代垃圾回收器的工作機制?
舉個栗子:
Java對象的一生:我是一個java對象,我出生在Eden區,在Eden區有一些跟我一樣的兄弟們,我們在Eden區中一起玩,每天都有新的兄弟進來。有一天Eden區中的人實在是太多了,我就被迫去了Survivor區的“From”區,自從去了Survivor區,在這里生活非常不穩定。有時候在Survivor的“From”區,有時候在Survivor的“To”區,居無定所。直到我15歲的時候(默認15歲),就被分配到年老代那邊,在這里人很多,并且年齡都挺大的。在年老代里,我生活了很久,每次GC年齡就+1,然后被回收。分代回收器有兩個分區:老生代和新生代,新生代默認的空間占比總空間的 1/3,老生代的默認占比是 2/3。
新生代使用的是復制算法,新生代里有 3 個分區:Eden、To Survivor、From Survivor,它們的默認占比是 8:1:1,它的執行流程如下:
在GC開始的時候,對象只會存在于Eden區和名為“From”的Survivor區,Survivor區“To”是空的。緊接著進行GC,Eden區中所有存活的對象都會被復制到“To Survivor區”,仍存活的對象會根據他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中。
清空 Eden 和 From Survivor 分區;
這時From Survivor 和 To Survivor 分區會互換角色,分區交換,From Survivor 變 To Survivor,To Survivor 變 From Survivor。
每次在 From Survivor 到 To Survivor 移動時都存活的對象,年齡就 +1,當年齡到達 15(默認配置是 15)時,升級為老生代。大對象也會直接進入老生代。
老生代當空間占用到達某個值之后就會觸發全局垃圾收回,一般使用標記整理的執行算法。以上這些循環往復就構成了整個分代垃圾回收的整體執行流程。
對象優先在 Eden 區分配:
多數情況,對象都在新生代 Eden 區分配。當 Eden 區分配沒有足夠的空間進行分配時,虛擬機將會發起一次 Minor GC。如果本次 GC 后還是沒有足夠的空間,則將啟用分配擔保機制在老年代中分配內存。
- Minor GC 是指發生在新生代的 GC,因為 Java 對象大多都是朝生夕死,所有 Minor GC 非常頻繁,一般回收速度也非常快;
- Major GC/Full GC 是指發生在老年代的 GC,出現了 Major GC 通常會伴隨至少一次 Minor GC。Major GC 的速度通常會比 Minor GC 慢 10 倍以上。
大對象直接進入老年代:
新生代使用的是標記-清除算法來處理垃圾回收的,如果大對象直接在新生代分配就會導致 Eden 區和兩個 Survivor 區之間發生大量的內存復制。因此對于大對象都會直接在老年代進行分配。
所謂大對象是指,需要大量連續內存空間的Java對象,最典型的大對象就是那種很長的字符串以及數組。大對象對虛擬機的內存分配來說就是一個壞消息,經常出現大對象容易導致內存還有不少空間時就提前觸發垃圾收集以獲取足夠的連續空間來 “安置” 它們。
虛擬機提供了一個XX:PretenureSizeThreshold參數,令大于這個設置值的對象直接在老年代分配,這樣做的目的是避免在Eden區及兩個Survivor區之間發生大量的內存復制(新生代采用的是復制算法)。
長期存活對象將進入老年代:
虛擬機采用分代收集的思想來管理內存,那么內存回收時就必須判斷哪些對象應該放在新生代,哪些對象應該放在老年代。因此虛擬機給每個對象定義了一個對象年齡的計數器,如果對象在 Eden 區出生,并且能夠被 Survivor 容納,將被移動到 Survivor 空間中,這時設置對象年齡為 1。對象在 Survivor 區中每過一次 Minor GC 年齡就加 1,當年齡達到一定程度(默認 15) 就會被晉升到老年代。
總結
以上是生活随笔為你收集整理的java垃圾回收机制_JVM的垃圾回收机制——垃圾回收算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python爬虫实例电商_价值上千元的P
- 下一篇: 坦克300征服者有没有无人驾驶功能