What's JVM-垃圾收集器與內存分配策略

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.1. 對象存在與否","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.1.1. 引用計數算法","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍏給對象添加一個計數器,每次引用就把計數器+1;引用失效,計數器-1;當計數器爲0,釋放對象。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍎但是它很難解決對象之間的循環引用問題。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.1.2. 可達性分析算法","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"選定一些對象作爲根節點,稱爲GC Roots,每次從根節點開始遍歷,遍歷後所有不可達節點(對象)就是不可用的,需要回收。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個從根節點開始的路徑鏈稱爲“引用鏈”。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":"none"},"content":[{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/23/2312cfaaa0f27ba472c709c72c7f5f6a.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":false,"pastePass":false}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然可達性分析判定一個對象是否應該回收取決於根節點是否可達,那麼根節點的選取就變得尤爲重要。在Java中,根節點(GC Roots)可以爲如下幾種:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"虛擬機棧引用的對象,比如各個線程調用的方法堆棧中的方法參數,局部變量,臨時變量。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"方法區中類靜態屬性引用的對象。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"方法區中常量引用的對象。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"本地方法棧中引用的對象。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"JVM內部的引用,比如基本數據類型對應的Class對象,常駐的異常對象。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"所有被同步鎖持有的對象。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"反應JVM內部情況的JMXBean,JVMTI中註冊的回調,本地代碼緩存等。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍐除了這些可以固定作爲GC Roots的對象之外,還有其他對象可以臨時性的加入。比如進行區域回收時,可能就需要把那些跨區域引用的對象的對象一塊放到GC Roots集合進行掃描。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實可以看到,所謂","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"GC Roots更像是活躍對象集合","attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"哪些對象是存活的,就可以作爲GC Roots","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.1.3. 再談引用","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍊Java原本對引用的定義和C的指針一樣:reference如果存儲的值表示一個內存的地址,那麼reference就是一個對象的引用。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍋但是在JDK2.0之後,覺得這樣的定義有點不妥,於是引入了四種引用類型。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它們分別是:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"強引用就是最傳統的定義,就是最基本的new對象然後賦值。任何時候,只要存在強引用,對象就不會被回收。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"軟引用就是那些非必需的對象,在內存即將用盡時,會把這些對象進行第二次回收,如果還不夠,則拋異常。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"弱引用更弱,它撐不過垃圾回收,任何只被弱引用關聯的對象都會在垃圾回收時被回收(","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"ThreadLocalMap","attrs":{}},{"type":"text","text":"的值就是這種類型,爲了防止內存泄漏而引入)。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"虛引用只能用來在對象被回收時得到一個通知,僅此而已。","attrs":{}}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.1.4. 回收方法區","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍌方法區的垃圾回收主要回收兩個部分:廢棄的常量和不再使用的類型(多用於動態代理生成的動態類型)。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍉回收常量很簡單,就是看是否還存在對它的引用。但是回收類型比較複雜且收益低。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於類型的回收,需要判斷一個類型是否屬於不再使用的類,條件會比較苛刻:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"該","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"類及其子類的所有實例都被回收","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"加載該","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"類的類加載器","attrs":{}},{"type":"text","text":"已經被回收。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"該類的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Class對象沒有在任何地方被引用","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在使用了大量的動態代理,反射,CGLib的地方,類型回收就顯得比較重要。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.2. 垃圾收集算法","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍇垃圾收集算法有引用計數式垃圾收集和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"追蹤式垃圾收集","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍓因爲當前主流的垃圾收集都是後者,所以下面的講解也是圍繞後者展開的。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.2.1. 分代收集理論","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🫐目前主流的理論(更多像是經驗總結)主要有兩個:弱分代假說,強分代假說。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍈弱分代假說:絕大多數對象都是朝生夕滅的(越往後越容易死亡)。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍒強分代假說:熬過越多次垃圾回收的對象越難死亡(越往後越不容易死亡)。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"收集器根據這兩個假說把Java堆劃分成了不同區域,根據對象的年齡(指對象熬過垃圾回收的次數)劃分出了兩個主要的區域:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"新生代","attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"老年代","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新生代關注的更多是如何保留少量存活,使用弱分代假說;而老年代則可以使用更低的頻率來回收,使用強分代假說。新生代回收之後剩下的對象會逐步晉升代老年代。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍑跨代引用假說:跨代引用相比於同代引用只佔少數。這個理論很容易得到推導:如果老年代引用了新生代,隨着引用的存在和老年代對象的長久存活,被它引用的新生代對象也會一直存活然後稱爲老年代。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲跨代引用假說的存在,所以可以把老年代劃分出不同區域,這樣在進行Minor GC時,僅僅把這些區域內的老年代加入到GC Roots,以它們爲根節點進行掃描。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"embedcomp","attrs":{"type":"table","data":{"content":"
收集方式解釋
Minor GC/Young GC僅對新生代進行垃圾回收
Major GC/Old GC只對老年代進行垃圾收集/對整堆進行垃圾收集
Mixed GC對整個新生代和部分老年代的垃圾收集
Full GC對整個Java堆和方法區的垃圾收集
"}}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.2.2. 標記-清除算法","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🥭標記就是判斷對象是否屬於垃圾的過程,這個可由可達性分析算法進行標記,在此不再描述。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍍清除就是把標記過的區域清空。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bf/bf96236ccfb50c0d0d8276278e8d3bc7.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個算法足夠簡單,但是它有兩個缺點:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"執行效率不穩定","attrs":{}},{"type":"text","text":",它的執行效率隨着堆中需要回收的對象數量增加而下降。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"第二個是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"碎片化問題","attrs":{}},{"type":"text","text":",過多的碎片會導致沒有足夠大的空間分配對象進而觸發進一步垃圾收集。","attrs":{}}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.2.3. 標記-複製算法","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決標記清除算法的執行效率不穩定問題,引入了標記-複製算法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🥥標記還是判斷對象是否需要回收。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🥝複製則是把需要保留的對象複製到另一半區域。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/77/77f4a582ad574f885f11ab7b08cb2b1b.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此算法會把堆分爲兩個區域,其中一個保留,另一個存放對象;每次垃圾回收就把需要存活的對象複製到另一半內存,然後清空整個內存半區,這樣這一半就完全空閒,而且所有的存活對象都會整齊地排列在另一半中。下次同樣這樣操作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種算法的缺點顯而易見:每次只有一半內存得以使用,未免有點太浪費空間了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決這個問題,可以把內存劃分比例換一下,因爲統計發現,絕大多數新生代對象撐不過第一輪垃圾回收。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前有一種更好的解決方案:把內存劃分成兩個較小的Survivor(以下簡稱S)和一個較大的Eden(以下簡稱E)。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"每次只使用一個S和一個E來分配內存","attrs":{}},{"type":"text","text":"。垃圾回收時,把這個E和S上的存活對象移動到另一個S上,然後清空E和剛剛那個S。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"凡事總有個例外,如果S不夠容納一次GC之後的存活對象,就需要老年代的部分區域來存放。這部分實現的安全性由JVM擔保。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由此可見這個算法主要用於新生代的GC。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.2.4. 標記-整理算法","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍅標記依舊是判斷對象是否需要回收。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🍆而整理則是把存活對象移動到一端,然後直接清除邊界以外的內存區域即可。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b5/b5c83cdcb915583e9ace193a7d65da16.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個算法如果用在老年代上的話,估計不是很理想,因爲老年代存活對象太多了。而且這個算法在移動對象時,必須暫停用戶程序,就像JOJO裏Dio喊出:The World!全世界凍結一樣。然後搬運它需要搬運的對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"是否移動對象有弊有利。移動的好處在於內存空間整齊,方便新空間的申請,且加大系統吞吐量;弊端就是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"會因爲移動對象而產生過多的停頓","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還有一種中和式,就是在內存碎片多到無法容忍時進行整理,CMS便是這樣的原理。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.3. HotSpot算法細節","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.3.1. 根節點枚舉","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🥑在JVM裏,固定可作爲根節點的有全局性引用和執行上下文(棧幀中的本地變量表)。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🥦迄今爲止,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"所有的根節點枚舉都是需要暫停用戶程序的","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🥬通過一種稱爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"OOPMap的數據結構,JVM可以快速得知所有引用的位置","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲在","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"類加載時會確定對象偏移量多少的位置上,數據是什麼類型","attrs":{}},{"type":"text","text":",加上即時編譯時,也會記錄棧和寄存器裏哪些保存的是引用類型,所以最終可以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"直接得到所有引用的位置","attrs":{}},{"type":"text","text":"。然後把它們加入到OOPMap,進而選出根節點。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JIT記錄OOPMap具體過程:一個線程意味着一個棧,一個棧由多個棧幀組成,一個棧幀對應着一個方法,一個方法裏面可能有多個安全點(見下面)。 GC發生時,程序首先運行到最近的一個安全點停下來,然後更新自己的OOPMap ,記下棧上哪些位置代表着引用。枚舉根節點時,遞歸遍歷每個棧幀的OOPMap,通過棧中記錄的被引用對象的內存地址,即可找到所有的GC Roots。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"OOPMap還可用作準確式GC(以下內容爲","attrs":{}},{"type":"link","attrs":{"href":"https://my.oschina.net/u/1757225/blog/1583822","title":"","type":null},"content":[{"type":"text","text":"轉載","attrs":{}}]},{"type":"text","text":")。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"保守式GC在進行GC的時候,會從一些已知的位置(GC Roots)開始掃描內存,掃描到一個數字就判斷他是不是可能是指向GC堆中的一個指針(這裏會涉及上下邊界檢查(GC堆的上下界是已知的)、對齊檢查(通常分配空間的時候會有對齊要求,假如說是4字節對齊,那麼不能被4整除的數字就肯定不是指針),之類的)。然後一直遞歸的掃描下去,最後完成可達性分析。這種模糊的判斷方法因爲無法準確判斷一個位置上是否是真的指向GC堆中的指針,所以被命名爲保守式GC。這種可達性分析的方式因爲不需要準確的判斷出一個指針,所以效率快,但是也正因爲這種特點,它存在下面兩個明顯的缺點:","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲是模糊的檢查,所以對於一些已經死掉的對象,很可能會被誤認爲仍有地方引用他們,GC也就自然不會回收他們,從而引起了無用的內存佔用,就是典型的佔着茅坑不拉屎,造成資源浪費。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於不知道疑似指針是否真的是指針,所以它們的值都不能改寫;移動對象就意味着要修正指針。換言之,對象就不可移動了。有一種辦法可以在使用保守式GC的同時支持對象的移動,那就是增加一個間接層,不直接通過指針來實現引用,而是添加一層“句柄”(handle)在中間,所有引用先指到一個句柄表裏,再從句柄表找到實際對象。這樣,要移動對象的話,只要修改句柄表裏的內容即可。但是這樣的話引用的訪問速度就降低了。Sun JDK的Classic VM用過這種全handle的設計,但效果實在算不上好。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"準確式GC","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與保守式GC相對的就是準確式GC,何爲準確式GC?就是我們準確的知道,某個位置上面是否是指針,對於Java來說,就是知道對於某個位置上的數據是什麼類型的,這樣就可以判斷出所有的位置上的數據是不是指向GC堆的引用,包括棧和寄存器裏的數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"網上看了下說是實現這種要求的方法有好幾種,但是在java中實現的方式是:從我外部記錄下類型信息,存成映射表,在HotSpot中把這種映射表稱之爲OOPMap,不同的虛擬機名稱可能不一樣。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實現這種功能,需要虛擬機的解釋器和JIT編譯器支持,由他們來生成OOPMap。生成這樣的映射表一般有兩種方式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每次都遍歷原始的映射表,循環的一個個偏移量掃描過去;這種用法也叫“解釋式”;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲每個映射表生成一塊定製的掃描代碼(想像掃描映射表的循環被展開的樣子),以後每次要用映射表就直接執行生成的掃描代碼;這種用法也叫“編譯式”。總而言之,GC開始的時候,就通過OOPMap這樣的一個映射表知道,在對象內的什麼偏移量上是什麼類型的數據,而且特定的位置記錄下棧和寄存器中哪些位置是引用。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.3.2. 安全點","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🥒因爲在一個程序裏,導致引用關係變化的指令非常多,所以不可能爲此一一記錄,那樣會造成過多空間來記錄。由此引入安全點。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🌶安全點的意義在於,引用關係時常變化,做不到對於所有引用關係的記錄,於是當需要GC時就把程序停靠在安全點上,然後統計GCRoots。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"安全點的選擇基本遵循“是否具有讓程序長時間執行的特徵”來選擇的,比如循環,異常拋出,方法調用等。只有具有這些特徵的指令纔會被放置安全點。說白了就是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果一段代碼會長時間執行","attrs":{}},{"type":"text","text":"(比如循環,或者方法調用),那麼我們","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"不能等到這段代碼執行完再添加安全點","attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"只能把安全點插入到這段代碼內部","attrs":{}},{"type":"text","text":",以此防止它長時間執行影響GC;比如插入在循環塊內最後的位置,方法返回的位置(相當於在方法與方法之間插入)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另一個問題是,怎麼讓程序在安全點停下來?有兩個方案,搶佔式中斷和主動式中斷。前者基本不再使用,來看看後者。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主動式中斷比較簡單,在安全點前面添加一個test彙編指令,當希望線程停下時,就通過JVM把test後面的地址設置爲不可讀,這樣就可以觸發線程產生自陷異常,被預先註冊好的處理程序掛起。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/29/29cc4ba053204a9c6d9b21eba855d50d.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這裏可以看出,所謂的枚舉GC Roots時發動The World能力是通過讓線程發生中斷來實現的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程中斷的位置是離它最近的安全點,所以GC開始枚舉時必須等待所有線程全部跑到最近的安全點纔行(此時忽略那些非運行的線程)。因爲安全點有很多,所以當JVM發動能力後,很快啊!所有還在運行的線程基本馬上立刻就停了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後更新OOPMap,進行枚舉GC Roots。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.3.3. 安全區域","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🌽安全點很好的解決了程序運行時的OOPMap更新問題,但是如果程序不在運行,而是在阻塞或者被調度離開CPU怎麼辦?此時就需要安全區域。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🥕安全區域指的是在某個代碼片段內,引用關係不會發生變化,因此在這個區域任何一個位置進行GC都是安全的。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"安全區域具體過程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1️⃣當程序進入安全區域時,首先進行標記,表明自己到了安全區域,此時GC就可以忽略這些在安全區域內的程序了;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2️⃣然後當它準備離開時,會判斷GC是否完成了根節點的枚舉,如果未完成,則等待,否則繼續執行。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.3.4. 記憶集","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🧄記憶集是爲了","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"解決跨代引用的問題","attrs":{}},{"type":"text","text":"而引入的,有了記憶集,就可以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"知道哪塊區域存在跨代引用","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"記憶集一般有這三種:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"1️⃣字長精度:精確代機器字長,就是處理器尋址位數,查看該字是否包含跨代指針。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"2️⃣對象精度:精確到每個對象,查看對象是否含有跨代指針。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"3️⃣卡精度:精確到某個內存區域,查看這個內存區域是否含有跨代指針。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中,卡精度是使用最多的,它有點像分頁,把內存劃分成固定大小的區域,然後","attrs":{}},{"type":"katexinline","attrs":{"mathString":"總內存大小/每個內存區域大小=卡表長度"}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果這塊內存區域含有跨代指針,則卡表對應元素爲1,否則爲0。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.3.5. 寫屏障","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"寫屏障的存在是爲了實現對卡表的更新,它使用類似AOP的技術。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,寫屏障會對引用賦值代碼進行AOP切入,使用的是環形通知(Around)。賦值代碼之前的部分是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"寫前屏障","attrs":{}},{"type":"text","text":",而之後的成爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"寫後屏障","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在應用了寫屏障技術後,JVM就會爲賦值指令生成相應的更新卡表指令。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0e/0ed094ea2521009199d52162f193ecf1.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是還有一個問題,稱爲","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"僞共享問題","attrs":{}},{"type":"text","text":"(緩存系統中是以緩存行(CacheLine)爲單位存儲的,當多線程修改互相獨立的變量時,如果這些變量共享同一個緩存行,就會無意中影響彼此的性能,這就是僞共享),這個問題在此不再描述。一種簡單的解決方案是:不採用無條件的寫屏障,而是使用判斷-更新操作來進行,每次","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"更新卡表之前,首先檢查卡表的值,如果是0,則更新爲1,否則不變","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3.3.6. 併發的可達性分析","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🧅根節點枚舉可以控制在範圍時間內,但是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"對堆內對象的標記會隨着對象的增加而增加","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🥔爲了保證標記時不會因爲引用關係變化而進行錯誤標記,所以依舊需要發動The World能力;如果堆內對象過多的話,就會導致時停過長,影響程序運行。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"暫停用戶程序的原因是爲了保證標記時不會發生引用關係變化,既然是爲了標記這段時間引用關係不變,那麼可以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"生成快照,然後在快照上進行","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是有什麼辦法可以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"在生成快照時也不會暫停用戶程序或者不會被運行時的用戶程序影響快照","attrs":{}},{"type":"text","text":"呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"引入了三色標記原理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先定義定義三個標記色:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"⚪️白色:表示對象","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"尚未被標記","attrs":{}},{"type":"text","text":",在標記開始之前,所有對象應該都是白色的。如果標記完成還是白色,表明對象不可達。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"🟤灰色:表示對象","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"已經被GC訪問過","attrs":{}},{"type":"text","text":",但是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"至少還有一個它引用的對象沒被標記","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"⚫️黑色:表示對象已被GC訪問過","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"且它引用的對象也全部被訪問過","attrs":{}},{"type":"text","text":",黑色對象表示可存活。GC Roots默認黑色。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d6/d6aad7cc2bda0a60ada45a3d22d6467d.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果無法保證快照一致性,那麼在標記時會出現兩種情況:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"原本是應該保留的對象,結果用戶程序刪除了引用關係,導致它應該被回收卻沒有回收,反應在圖中就是所有指向某個黑色節點的引用全部消失了。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"原本是應該刪除的對象,結果用戶程序又重新引用了,導致它應該被保留卻被回收了,反應在圖中就是某個白色節點在掃描完成後突然有了指向它的引用。這種情況比第一種","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"更加危險","attrs":{}},{"type":"text","text":",因爲未回收的對象,可以在下一次GC回收,但是如果應該保留的對象被刪除了,就會造成空指針異常。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一種拋棄不說,因爲這體現出來的僅僅是回收不及時(下次就會回收)罷了,但是第二種需要格外注意。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於第二種情況產生的原因,有兩個:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"賦值操作添加了一條或多條從黑色對象到白色對象的引用。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"賦值操作刪除了全部從灰色對象到某個白色對象的直接或間接引用。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了解決誤刪問題,就需要破壞這兩個條件。於是引入","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"增量更新","attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"原始快照","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"增量更新:當有黑色對象向白色對象添加引用時,就記錄,然後在掃描完成後,以剛剛那些黑色對象爲根,再進行一次掃描。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原始快照:當有灰色對象想刪除對白色對象的引用時,就記錄下來,掃描結束,以灰色對象爲根,再來一次掃描。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一句話:增量更新就是你想增加存活對象我就給你記錄然後增加;原始快照就是你想刪除可能存活的對象我就記錄,不給你刪,想刪嗎?等下次GC吧!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不論是增量更新還是原始快照,對它們(增量更新或原始快照記錄的變化)進行處理","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"依舊需要暫停用戶程序","attrs":{}},{"type":"text","text":",但是因爲它們量比較少,所以產生的停頓時間可以暫時忽略。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是在併發條件下的分析,如果回收器不是併發的,而是直接發動時停能力,那就直接掃描標記,也不存在上述問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對了,引用關係的刪除和添加的記錄操作是通過寫屏障實現的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"標記完了,具體的回收行爲取決於具體的回收器。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.5. 經典垃圾收集器","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/53/53d3ff9c7a342e16ca6d3969c23e3d90.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"⚽️Serial收集器。負責對新生代進行收集,使用標記-複製算法。它在運行時必須暫停用戶程序,因此可能交互體驗不太好,也是客戶端默認的收集器。但是它是單線程的,所以適合單核處理器或核數較少的情形;加之內存消耗最小,沒有線程交互的開銷,是一款很經典的收集器。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/23/23cd719cd4cb6944dc87a164640865c1.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"🏀SerialOld收集器。同樣是單線程的,前者收集器的老年代版本。所以使用標記-整理算法。除此之外,它還可以作爲CMS發生失敗時的預選方案(見後述)。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/23/23cd719cd4cb6944dc87a164640865c1.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":3,"normalizeStart":3},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"🏈ParNew收集器。是Serial的並行版本,除此之外和Serial沒什麼區別。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2d/2d4f3a52dd00872d067fba23b7780a67.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":4,"normalizeStart":4},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"⚾️ParallelScavenge。作用於新生代,多線程,使用標記-複製算法,但是它和ParNew的區別在於,它更注重吞吐量而不是延遲。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"🏐ParallelOld收集器。作用於老年代,是ParallelScavenge的老年代版本,基於標記-整理算法。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c8/c8c88421e73917fb44ffcb1d09668da1.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":6,"normalizeStart":6},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"🏓CMS收集器。關注響應速度,以停頓時間爲目標,使用標記-清除算法。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5c/5c54403ea5688026dbfe4f4b79b82e8d.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它的實現略微有點複雜,但是它剛好體現了前面討論的併發的可達性分析原理。整體分爲四個步驟:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"A. 🔧","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"初始標記","attrs":{}},{"type":"text","text":"。僅僅標記一下GC Roots能直接關聯到的對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"B. 🔨","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"併發標記","attrs":{}},{"type":"text","text":"。與用戶線程一起執行,遍歷整個對象圖,同時記錄這期間用戶程序作出的更改。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"C. ⛏","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"重新標記","attrs":{}},{"type":"text","text":"。修正併發標記期間用戶程序產生的修改,就是把併發期間標記爲不可達的但因爲引用關係變化而又可達的對象,重新標記爲可達。這一步需要暫停用戶程序。但是因爲數量少,所以並不會花費很多時間。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"D. 🛠","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"併發清除","attrs":{}},{"type":"text","text":"。回收對象,由於不需要移動對象,所以也是可以於用戶程序並行執行的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是它也有三個明顯的缺點:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一、🙅🏻對處理器資源很敏感。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"二、🙅🏼","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"無法處理浮動垃圾","attrs":{}},{"type":"text","text":"(因爲標記是併發的,所以標記過程可能還有垃圾產生,這隻能留到下一次回收),導致內存空間被垃圾佔用,如果不幸的話,甚至可能導致在併發標記階段預留的堆大小不夠用戶程序使用。這樣就會觸發“併發失敗”;此時將會凍結用戶線程,使用SerialOld進行老年代垃圾收集。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"三、🙅標記-清除算法會導致","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"空間碎片","attrs":{}},{"type":"text","text":",此時不得不進行一個Full GC(整堆和方法區收集)。這樣會導致空間整理時產生更長的停頓。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於浮動垃圾,其產生是因爲重新標記只會處理併發標記階段被記錄的變化中從不可達->可達的對象,反之不會處理,所以纔會產生浮動垃圾;要麼就是併發標記的記錄不會記錄可達->不可達的變化。總之,要麼是沒記錄新的垃圾,要麼是記錄了但是沒處理新的垃圾。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上僅爲本人理解,爲防誤導請讀者自行谷歌。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":7,"normalizeStart":7},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"🎱G1收集器。G1是一個里程碑意義的收集器,其重要意義在於開創了局部收集的設計思路和基於Region的內存佈局。它還是一款主要面向服務端的垃圾收集器。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"G1爲了更好地回收,取消了傳統的新生代和老年代的概念,回收的範圍擴大到了整個堆。最後通過特殊的算法判定哪個區域回收收益最大,組成","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"回收集","attrs":{}},{"type":"text","text":"進行回收。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Region中有一類特殊的Humongous區域,專門用來存放大對象。大對象可以跨區存儲,即使用連續的Region來存儲。G1的大多數行爲都把Humongous當初老年代來處理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"G1還把Region當成最小回收單位,每次優先回收價值最大的Region。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看一下G1的內存佈局:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/00/008685c29d41a8bb4bd0b54d4159d254.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於Region的跨引用問題,G1的解決方案是進行跨區域記錄且使用更加複雜的記憶集,具體表現爲:誰指向我和我指向誰;是一個雙向的映射集。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"G1還使用","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"原始快照","attrs":{}},{"type":"text","text":"(SATB)算法來保證併發標記過程中的引用變化,同時爲了保證收集過程中的新對象分配,設置了兩個TAMS指針,指針上的區域G1默認是標記過的,所以新的對象可以分配在指針之上來確保存活。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還有一些其他的問題,可以參考","attrs":{}},{"type":"link","attrs":{"href":"https://tech.meituan.com/2016/09/23/g1.html","title":"","type":null},"content":[{"type":"text","text":"G1的原理實現","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在來看看G1的實際工作過程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"A. 📪","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"初始標記","attrs":{}},{"type":"text","text":"。僅僅標記一下GC Roots能直接關聯到的對象,並設置新的TAMS指針的值。此階段可以藉助Minor GC同步完成,所以耗時幾乎忽略不計。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"B. 📫","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"併發標記","attrs":{}},{"type":"text","text":"。掃描並標記整個對象圖,並使用SATB進行記錄此過程中的引用變化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"C. 📬","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"最終標記","attrs":{}},{"type":"text","text":"。暫停用戶程序,處理SATB記錄。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"D. 📭","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"篩選回收","attrs":{}},{"type":"text","text":"。重新更新Region統計數據並排序,或通過用戶自定的策略選定Region(s)構成回收集,然後把存活Region複製到空的Region(自動整理內存空間)並清空舊的區域,這個移動對象的過程需要暫停用戶程序。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ab/ab153c2847a1b6f0df856d702a28cf79.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用戶可以選擇停頓時間是G1的一個很重要的特性。可以在停頓時間和吞吐量之間取得一個實際的平衡。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時爲了實現原始快照搜索,G1還使用了寫前屏障,不過由於G1的寫屏障更復雜,所以使用的是異步實現。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.6. 低延遲垃圾收集器","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b7/b7080cb607981ea7e39e63d0593d70e2.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Shenandoah","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"和G1一樣,Shenandoah支持Region,支持Humongous和回收價值最大的,但是Shenandoah支持回收階段與用戶線程併發,默認不使用分代收集,使用連接矩陣而不是記憶集。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"連接矩陣很簡單,就是一個M*N的表格,如果i和j存在引用關係,那麼i-j就被打上標記。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來看看Shenandoah的工作原理:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🚗初始標記:和G1一樣,找到GC Roots可以直接訪問到的,需要","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"時停","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🚕併發標記:併發標記,遍歷圖,找到不可達節點,不需要時停。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🚙最終標記:和G1一樣,處理SATB掃描。並統計回收價值最高的Region,生成回收集。會產生小時段","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"時停","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🚌併發清理:清理那些整個Region沒有一個存活對象的Region。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🚎併發回收:把回收集中的對象複製到空白Region中,通過“轉發指針”解決,不需要時停,這也是Shenandoah最不同的地方。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🏎初始引用更新:把堆中指向舊對象的引用修正到新的對象上去,這個階段並不做實際工作,僅僅建立線程集合點並確保所有併發回收階段的線程完成了對象移動工作,非常短暫的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"時停","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🚓併發引用更新:與用戶線程併發,取決於內存中涉及的引用數量;僅需按照地址序更新引用值。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🚑最終引用更新:修正對GC Roots的引用,需要","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"時停","attrs":{}},{"type":"text","text":",取決於GC Roots的數量。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🚒併發清理:釋放回收集中的Region。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所謂引用指針。就是給對象頭添加一個指針,正常下這個指針指向自己,此方式有點像前面提到的句柄定位。更新對象引用時,僅僅需要更改這一處即可。讓每次訪問舊對象時,自動轉發到新對象位置,這樣只要舊對象還在,就會訪問到新的對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是轉發指針必然存在併發問題,比如收集器線程設置指針,而用戶線程訪問,還有就是對於對象的寫入,只能寫入到新對象上。對於併發問題,可以使用CAS+失敗重試解決。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於轉發指針的設置,是在併發回收階段處理的,然後在最終引用更新更新引用(指針)的值。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要覆蓋全部對象訪問操作,Shenandoah必須使用讀,寫屏障去攔截。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於讀寫屏障,尤其是讀屏障,性能代價是很大的,因爲系統中讀操作更多。可以改成基於引用訪問屏障來解決,也就是僅攔截引用類型的讀寫操作,而忽略普通類型。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ZGC","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ZGC是一款基於Region內存佈局,不設分代(暫時)。使用了讀屏障,染色指針和多重內存映射技術來實現可併發的標記-整理算法,以低延遲爲目標的一款垃圾收集器。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ZGC的Region分爲三個區域:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1️⃣小型Region:固定爲2MB,用於放置<256KB的小對象。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2️⃣中型Region:固定爲32MB,用於放置256KB<=對象大小<4MB。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3️⃣大型Region:容量可以變化,但必須是2MB的整數倍,用於放置>=4MB的對象,每個大型Region只會放置一個大對象。大型Region不會被重分配。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d9/d9893e8c0cfe781636196efde1b0d699.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ZGC的另一個標誌性技術是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"染色指針","attrs":{}},{"type":"text","text":"。之前的三色標記法,看起來是標記對象,實則可以是標記引用,什麼意思呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"“讓所有指向對象的指針失效可以達到標記對象爲不存活的效果。”","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"一個對象的存活與否,取決於是否有引用指向它","attrs":{}},{"type":"text","text":",所以如果可以通過設置引用(地址指針)某些bit位來指明引用的狀態,也是一樣的標記作用。如果","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"指向這個對象的所有指針都被標記爲不可用,那麼這個對象就是需要回收的","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時三色標記成了一開始讓每個指針的標記位都爲不可達,遍歷一遍引用圖,把可達的引用標記位設爲可達,最後還是不可達的引用指向的對象(指向這個對象的所有引用都不可達)就是需要回收的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而染色指針就是把記錄指針是否可用的機制放在了指針構成上。爲什麼可以這樣?因爲目前Linux支持的最大虛擬地址空間爲47位,也就是128TB,和最大物理地址空間46位,也就是64TB。而在64位機中,Linux保留了高18位,如果我們可以縮減堆地址的話,那就會有部分高位怎麼也用不到。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7d/7d2079a3e4c6cde7d77817d711c47899.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/89/89ea1561de319cef53ff6f1ba27aaa41.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ab/ab0389856a1c188b79e7316d3de334e8.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9d/9d1761ae18b3546fde6aca42db5f9f7b.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以ZGC最高支持4TB堆和64位機,這樣第43-46位就可爲我們所用(實際上目前ZGC支持到了16TB,這個是後話)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"把4位分成:保留位-Remapped位-Mark1位-Mark0位這四種便可以實現把引用狀態信息保存在指針中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"染色指針有三大優勢:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"某個Region一旦被移走就立刻可用,因爲移走之後指向這個位置的指針被標記爲Remapped,接下來通過“自愈”技術修復即可。所以下一次訪問這裏的指針會自動重定向到新的位置。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"是一種可擴展的結構,方便日後提高性能。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"可以大幅減少內存屏障使用的數量。因爲ZGC不需要記錄跨代引用,其一是它可以把這些信息記錄在指針裏,二是它不支持跨代引用。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是有一個問題,Java作爲一個進程,直接修改內存指針,OS是否支持?事實上,因爲虛擬地址到物理地址的映射關係,加上ZGC使用了空間換時間,使得這個問題得以解決。所謂空間換時間,只是把三(如果算上原本的地址就是四個)個地址映射到了同一個物理地址空間。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,OS在做虛擬->物理空間映射時,把地址後m位提出來,把前64-m位當成頁表索引,找到n,再把n+m拼接到一起(不是加到一起),作爲物理地址,n的範圍,m的值由OS和實際內存大小決定。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以可以知道,如果後m位一致,那麼多個虛擬地址會映射到同一個物理地址的。","attrs":{}},{"type":"link","attrs":{"href":"https://juejin.cn/post/6845166890763943943","title":"","type":null},"content":[{"type":"text","text":"詳見","attrs":{}}]},{"type":"text","text":"。所以ZGC利用這一點,把四個標誌位不同的虛擬地址提前申請了,讓它們都映射到同一個物理地址,解決了OS不支持的問題。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c7/c7e812dd8d6464b80830cabe669ce284.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果把4位標示位看成內存分段符,算上原本的,四位標識位,有0000,0001,0010,0100四種,所以它們在後42位相同的情況下,有0+4TB,4+4TB,8+4TB,16+4TB的內存空間起始間隔。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在來看看實際的工作流程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e1/e17684d7f638920eeb49c7ebfede051b.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⌚️併發標記。做圖的可達性分析,類似於G1和Shenandoah的初始標記,最終標記。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"此階段會更新染色指針的Mark0和Mark1標誌位","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"💻併發預備重分配。根據特定的查詢條件","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"找出哪些Region需要清理","attrs":{}},{"type":"text","text":",並","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"組成重分配集","attrs":{}},{"type":"text","text":"。ZGC通過全堆掃描獲取重分配集,替代了G1維護記憶集的成本。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"🖥併發重分配。這是ZGC的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"核心階段","attrs":{}},{"type":"text","text":"。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"首先","attrs":{}},{"type":"text","text":"把重分配集中的存活對象","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"複製到新的Region中","attrs":{}},{"type":"text","text":",並維護一個轉發表,用來記錄對象舊地址到對象新地址的映射。因爲染色指針的特點,ZGC可以通過引用(指針值)得知一個對象是否處於重分配集。如果程序","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"訪問處於重分配集中的對象","attrs":{}},{"type":"text","text":",會被內存屏障捕獲,然後","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"轉發到新的對象位置去","attrs":{}},{"type":"text","text":",同時","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"更新引用的值","attrs":{}},{"type":"text","text":"。這樣的話,只有第一次訪問被移動的對象會陷入處理,往後都一步到位,這樣的話比Shenandoah的轉發指針負載更小一些。這個稱爲ZGC的“自愈”,一旦某個Region裏的","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"對象全部複製完畢","attrs":{}},{"type":"text","text":",便可","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"釋放","attrs":{}},{"type":"text","text":",但是保留轉發表,以防其他對舊對象的引用找不到新對象在哪。無論某個舊對象還有多少個引用,都可以通過“自愈”技術解決。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"📱併發重映射。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"修改整堆中指向重分配集中的舊對象的引用","attrs":{}},{"type":"text","text":",這麼做的目的是爲了讓程序不變慢,以及完全修正後釋放轉發表的收益。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"還有一個問題,就是ZGC取消了新生代,所以在對象生命週期很短的場景下,可能回收的不會那麼及時。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時還有一個優點,在支持NUMA的處理器上,會優先在當前線程所在的CPU核心本地緩存上分配,以保證高效的內存訪問。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"參考","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.infoq.com/articles/G1-One-Garbage-Collector-To-Rule-Them-All/","title":"","type":null},"content":[{"type":"text","text":"G1: One Garbage Collector To Rule Them All","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://shipilev.net/talks/devoxx-Nov2017-shenandoah.pdf","title":"","type":null},"content":[{"type":"text","text":"Shenandoah GC","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://cr.openjdk.java.net/~pliden/slides/ZGC-Jfokus-2018.pdf","title":"","type":null},"content":[{"type":"text","text":"The Z Garbage Collector","attrs":{}}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章