1. 程式人生 > 遊戲資訊 >《龍之國物語攻略》4月6日版本更新公告

《龍之國物語攻略》4月6日版本更新公告

一:概述

垃圾收集(Garbage Collection) 簡稱GC
垃圾收集器關注的是java堆和方法區的記憶體該如何管理,因為這兩個區域有著很顯著的不確定性:一個介面的多個實現類需要的記憶體可能不一樣,一個方法所執行的不同條件分支所需要的記憶體也可能不一樣,這部分記憶體的分配和回收是動態的。

記憶體分配
 Java 的自動記憶體管理主要是針對物件記憶體的回收和物件記憶體的分配。同時,Java 自動記憶體管理最核心的功能是 記憶體中物件的分配與回收。
 Java 堆是垃圾收集器管理的主要區域,因此也被稱作GC 堆(Garbage Collected Heap),從垃圾回收的角度,由於現在收集器基本都採用分代垃圾收集演算法,所以 Java 堆還可以細分為:新生代和老年代:再細緻一點有:Eden 空間、From Survivor、To Survivor 空間等。進一步劃分的目的是更好地回收記憶體,或者更快地分配記憶體。


堆空間基本結構

二:如何判斷物件存亡

概述
 垃圾回收顧名思義就是將已分配出去的,但卻不再使用的記憶體回收回來,以便能夠再次分配,在Java虛擬機器的語境下,垃圾指的是死亡的物件所佔據的堆空間。那麼怎麼來判斷一個物件是死亡還是存活的是進行垃圾回收的關鍵。
方法
 判斷物件是否死亡的兩種方式:引用計數法可達性分析演算法

2.1 引用計數法

 引用計數演算法的思想非常簡單:在物件中新增一個引用計數器,每當有一個地方引用了它時,計數器值就加一,當引用失效時,計數器值就減一,任何時刻計數器為零時說明該物件已死亡,便可以被回收了。
 但是在主流的java虛擬機器裡面沒有選用引用計數演算法來管理記憶體,因為使用引用計數演算法除了需要額外的空間儲存計數器,以及繁瑣的更新操作,更重要的是無法處理迴圈引用物件。當物件 a 與 b 互相引用,除此之外沒有其他引用指向a 或者 b,實際上這兩個物件已經不可能再被訪問,但是它們相互引用對方,導致它們的程式計數器都不為0,那麼這兩個物件所佔據的空間將不可回收,從而造成記憶體洩漏。

2.2 可達性分析演算法

 可達性分析演算法的實質是將稱為"GC roots"的根物件作為存活物件集合,從這些節點開始根據引用關係向下搜尋,搜尋過程走過的路徑稱為引用鏈,如果某個物件沒有任何引用鏈相連,則此物件是不會再被使用的,將會被判定為可回收的物件。

那什麼是GC Roots呢,我們可以理解為由堆外指向堆內的引用,固定可以作為GC Roots的物件包括(但不限於)以下幾種:
  1.在虛擬機器棧中引用的物件,例如當前正在執行的方法所使用到的引數、區域性變數、臨時變數等。
  2.已載入類的靜態變數
  3.在方法區中常量引用的物件,如字串常量池裡面的引用
  4.本地方法棧中 JNI(通常說的 Native 方法)引用的物件
  5.所有被同步鎖(synchronized關鍵字)持有的物件
  6.根據使用者選用的垃圾收集器以及當前回收的記憶體區域不同,臨時性加入的物件,共同構成完整的GC Roots集合。
 可達性分析演算法在多執行緒的環境下,其他執行緒可能會更新已經訪問過的物件中的引用,從而造成誤報(將引用設定為null)或者漏報(將引用設定為未被訪問過的物件)。漏報可能會使得垃圾回收器可能回收事實上仍被引用的物件記憶體,可能導致Java虛擬機器崩潰。

三:引用

概述
 無論通過引用計數演算法判斷物件的引用數量,還是通過可達性分析演算法判斷物件是否引用鏈可達,判斷物件是否存活都和 “引用” 離不開關係。在jdk1.2之後,java對引用的概念進行了擴充,將引用分為了強引用、軟引用、弱引用、虛引用,(引用強度依次逐漸減弱)。
強引用(StrongReference):
 是指程式程式碼之中普遍存在的引用賦值,例如(Object obj = new Object),只要強引用關係還存在,垃圾收集器就永遠不會回收掉被引用的物件,當記憶體空間不足,Java 虛擬機器寧願丟擲 OutOfMemoryError 錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足問題。
軟引用(SoftReference):
 描述一些還有用,但非必須的物件,如果記憶體空間足夠,垃圾回收器就不會回收它,如果記憶體空間不足了,會把這些物件列進回收範圍之中進行第二次回收,如果這次回收還沒有足夠的記憶體,才會丟擲記憶體溢位異常。只要垃圾回收器沒有回收它,該物件就可以被程式使用。軟引用可用來實現記憶體敏感的快取記憶體。
 軟引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被垃圾回收,JAVA 虛擬機器就會把這個軟引用加入到與之關聯的引用佇列中。
弱引用:
 描述非必須物件,被弱引用關聯的物件只能存活到下一次垃圾收集發生為止,只具有弱引用的物件擁有更短暫的生命週期。在垃圾回收器執行緒掃描它所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。
 弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被垃圾回收,Java 虛擬機器就會把這個弱引用加入到與之關聯的引用佇列中
虛引用:
 虛引用是最弱的一種引用關係,一個物件是否有虛引用的存在完全不會對其生命週期構成影響,也無法通過虛引用來取得一個物件例項,任何時候都能被垃圾回收。給物件設定虛引用的唯一目的只是為了能在這個物件被收集器回收時收到一個系統通知,主要用來跟蹤物件被垃圾回收的活動。
注意:
 在程式設計中一般很少使用弱引用與虛引用,使用軟引用的情況較多,這是因為軟引用可以加速 JVM 對垃圾記憶體的回收速度,可以維護系統的執行安全,防止記憶體溢位(OutOfMemory)等問題的產生。

四:垃圾收集演算法


從如何判斷物件消亡的角度出發,垃圾收集演算法可以分為“引用計數式垃圾收集”“追蹤式垃圾收集”,也被稱為“直接垃圾收集”間接“垃圾收集”。
新生代:每次垃圾收集都發現有大批物件死去,而每次回收後存活的少量物件,將會逐步晉升到老年代中存放。

4.1 標記-清除演算法

標記清除演算法可以分為"標記""清除"兩個階段,可以標記出所有需要回收的物件,標記完成後統一回收掉所有被標記的物件。也可以反過來,標記存活的物件,統一回收所有未被標記的物件,而標記過程就屬於垃圾的判定過程。
缺點:
 1.執行效率不穩定,當Java堆中需要對大量物件進行回收的時候,標記和清除兩個過程執行效率都隨著物件數量增長而降低。
 2.標記、清除後產生大量不連續的記憶體碎片,導致以後程式執行過程中需要分配較大的物件時無法找到足夠的連續記憶體空間而不得不提前觸發另一次垃圾收集。
標記-清除演算法的執行過程:

4.2 標記-複製演算法

為了解決面對大量可回收物件時執行效率低的問題,“標記-複製”收集演算法出現了。它可以將記憶體分為大小相同的兩塊,每次使用其中的一塊。當這一塊的記憶體使用完後,就將還存活的物件複製到另一塊去,然後再把使用的空間一次清理掉。這樣就使每次的記憶體回收都是對記憶體區間的一半進行回收。
缺點:
 空間浪費,堆空間的使用效率極低,將可用的記憶體縮小為了原來的一半。
標記-複製演算法的執行過程:

4.3 標記-整理演算法

由於在老年代中可能存在所有物件都100%存活的極端情況,所以一般不能直接使用標記-複製演算法。而標記-整理演算法是根據老年代的特點提出的一種標記演算法,標記過程仍然與“標記-清除”演算法一樣,但後續步驟不是直接對可回收物件回收,而是讓所有存活的物件向一端移動,然後直接清理掉端邊界以外的記憶體。
缺點:
 1.在老年代這種每次回收都有大量物件存活的區域,移動存活物件並更新所有引用這些物件的地方將是極為負重的操作,而且這種操作必須全程暫停使用者應用程式才能進行。這樣的停頓被稱為"Stop The World"。
標記-整理演算法的執行過程:

4.4 分代收集演算法

當前虛擬機器的垃圾收集都採用分代收集演算法,這種演算法只是根據物件存活週期的不同將記憶體分為幾塊。一般將 java 堆空間分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集演算法。
新生代:用來儲存新建的物件。被劃分為 Eden 區,以及兩個大小相同的 Survivor區。在Java虛擬機器中可以動態的分配Eden區和Survivor區(Java虛擬機器引數:-XX:+UserPSAdaptiveSurivivorSizePolicy),也可以固定其比例(Java虛擬機器引數:-XX:SurvivorRatio)。
老年代:當物件存活時間夠長時,將其移動到老年代。

在我們使用new指令的時候,它會在Eden區域劃分一塊作為儲存物件的記憶體,由於堆空間是執行緒共享的,因此直接劃空間是需要進行同步的。
 在新生代中,每次收集都會有大量物件死去,所以可以選擇”標記-複製“演算法,只需要付出少量物件的複製成本就可以完成每次垃圾收集。
 而老年代的物件存活機率是比較高的,而且沒有額外的空間對它進行分配擔保,所以我們必須選擇“標記-清除”或“標記-整理”演算法進行垃圾收集。

五:回收方法區

方法區垃圾收集主要回收的兩部分內容:廢棄的常量不再使用的型別
判斷廢棄的常量:
  1.沒有任何字串物件引用常量池中的 "java" 常量。
  2.虛擬機器中也沒有其他地方引用這個字面量。
判斷不再使用的類:
  1.該類的所有例項都已經被回收,也就是java堆中不存在任何該類以及派生子類的例項。
  2.載入該類的類載入器以及被回收。
  3.該類對應的 java.lang.Class 物件沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
java虛擬機器被允許對滿足條件的無用類進行回收,這裡說的是僅僅,並非沒了引用就必然會被回收。在大量使用反射、動態代理、CGLib等位元組碼框架,動態生成 JSP 以及OSGi 這類頻繁自定義類載入器的場景中,通常都需要虛擬機器具備型別解除安裝的能力,以保證不會對方法區造成過大的記憶體壓力。

小結