作者:林振華
來源:編程原理

JVM核心知識體系知識清單(上)

4.3應用場景

通過前文的剖析我們已經非常清楚類加載器的工作原理,那麼我們該如何利用類加載器的特點,最大限度的發揮它的作用呢?

4.3.1熱部署

背景

熱部署這個詞彙我們經常聽說也經常提起,但是卻很少能夠準確的描述出它的定義。說到熱部署我們第一時間想到的可能是生產上的機器更新代碼後無需重啓應用容器就能更新服務,這樣的好處就是服務無需中斷可持續運行,那麼與之對應的冷部署當然就是要重啓應用容器實例了。還有可能會想到的是使用IDE工具開發時不需要重啓服務,修改代碼後即時生效,這看起來可能都是服務無需重啓,但背後的運行機制確截然不同,首先我們需要對熱部署下一個準確的定義。

  • 熱部署(Hot Deployment):熱部署是應用容器自動更新應用的一種能力。

首先熱部署應用容器擁有的一種能力,這種能力是容器本身設計出來的,跟具體的IDE開發工具無關。而且熱部署無需重啓服務器,應用可以保持用戶態不受影響。上文提到我們開發環境使用IDE工具通常也可以設置無需重啓的功能,有別於熱部署的是此時我們應用的是JVM的本身附帶的熱替換能力(HotSwap)。熱部署和熱替換是兩個完全不同概念,在開發過程中也常常相互配合使用,導致我們很多人經常混淆概念,所以接下來我們來剖析熱部署的實現原理,而熱替換的高級特性我們會在下文字節碼增強的章節中介紹。

原理

從熱部署的定義我們知道它是應用容器蘊含的一項能力,要達到的目的就是在服務沒有重啓的情況下更新應用,也就是把新的代碼編譯後產生的新類文件替換掉內存裏的舊類文件。結合前文我們介紹的類加載器特性,這似乎也不是很難,分兩步應該可以完成。由於同一個類加載器只能加載一次類文件,那麼新增一個類加載器把新的類文件加載進內存。此時內存裏面同時存在新舊的兩個類(類名路徑一樣,但是類加載器不一樣),要做的就是如何使用新的類,同時卸載舊的類及其對象,完成這兩步其實也就是熱部署的過程了。也即是通過使用新的類加載器,重新加載應用的類,從而達到新代碼熱部署。

實現

理解了熱部署的工作原理,下面通過一系列極簡的例子來一步步實現熱部署,爲了方便讀者演示,以下例子我儘量都在一個java文件裏面完成所有功能,運行的時候複製下去就可以跑起來。

  • 實現自定義類加載器


參考4.2.2中自定義類加載器區別系統默認加載器的案例,從該案例實踐中我們可以將相同的類(包名+類名),不同”版本“(類加載器不一樣)的類同時加載進JVM內存方法區。

  • 替換自定義類加載器


既然一個類通過不同類加載器可以被多次加載到JVM內存裏面,那麼類的經過修改編譯後再加載進內存。有別於上一步給出的例子只是修改對象的值,這次我們是直接修改類的內容,從應用的視角看其實就是應用更新,那如何做到在線程運行不中斷的情況下更換新類呢?

下面給出的也是一個很簡單的例子,ClassReloading啓動main方法通過死循環不斷創建類加載器,同時不斷加載類而且執行類的方法。注意new MyClassLoader(“target/classes”)的路徑更加編譯的class路徑來修改,其他直接複製過去就可以執行演示了。

JVM核心知識體系知識清單(中)

ClassReloading線程執行過程不斷輪流注釋say()和ask()代碼,然後編譯類,觀察程序輸出。

如下輸出結果,我們可以看出每一次循環調用都新創建一個自定義類加載器,然後通過反射創建對象調用方法,在修改代碼編譯後,新的類就會通過反射創建對象執行新的代碼業務,而主線程則一直沒有中斷運行。讀到這裏,其實我們已經基本觸達了熱部署的本質了,也就是實現了手動無中斷部署。但是缺點就是需要我們手動編譯代碼,而且內存不斷新增類加載器和對象,如果速度過快而且頻繁更新,還可能造成堆溢出,下一個例子我們將增加一些機制來保證舊的類和對象能被垃圾收集器自動回收。

what is your name
what is your name
what is your name//修改代碼,編譯新類
my name is lucy
my name is lucy
what is your name//修改代碼,編譯新類
  • 回收自定義類加載器


通常情況下類加載器會持有該加載器加載過的所有類的引用,所有如果類是經過系統默認類加載器加載的話,那就很難被垃圾收集器回收,除非符合根節點不可達原則纔會被回收。

下面繼續給出一個很簡單的例子,我們知道ClassReloading只是不斷創建新的類加載器來加載新類從而更新類的方法。下面的例子我們模擬WEB應用,更新整個應用的上下文Context。下面代碼本質上跟上個例子的功能是一樣的,只不過我們通過加載Model層、DAO層和Service層來模擬web應用,顯得更加真實。

JVM核心知識體系知識清單(中)

JVM核心知識體系知識清單(中)

輸出結果跟上一個例子相似,可以自己運行試試。我們更新業務方法編譯通過後,無需重啓main方法,新的業務就能生效,而且也解決了舊類卸載的核心問題,因爲context的應用對象的跟節點,context是由我們自定義類加載器所加載,由於User/Dao/Service都是依賴context,所以其類也是又自定義類加載器所加載。根據GC roots原理,在創建新的自定義類加載器之後,舊的類加載器已經沒有任何引用鏈可訪達,符合GC回收規則,將會被GC收集器回收釋放內存。至此已經完成應用熱部署的流程,但是細心的朋友可能會發現,我們熱部署的策略是整個上下文context都替換成新的,那麼用戶的狀態也將無法保留。而實際情況是我們只需要動態更新某些模塊的功能,而不是全局。這個其實也好辦,就是我們從業務上把需要熱部署的由自定義類加載器加載,而持久化的類資源則由系統默認類加載器去完成。

  • 自動加載類加載器


其實設計到代碼設計優雅問題,基本上我們拿出設計模式23章經對號入座基本可以解決問題,畢竟這是前人經過千萬實踐錘鍊出來的軟件構建內功心法。那麼針對我們熱部署的場景,如果想把熱部署細節封裝出來,那代理模式無疑是最符合要求的,也就是咱們弄出個代理對象來面向用戶,把類加載器的更替,回收,隔離等細節都放在代理對象裏面完成,而對於用戶來說是透明無感知的,那麼終端用戶體驗起來就是純粹的熱部署了。至於如何實現自動熱部署,方式也很簡單,監聽我們部署的目錄,如果文件時間和大小發生變化,則判斷應用需要更新,這時候就觸發類加載器的創建和舊對象的回收,這個時候也可以引入觀察者模式來實現。由於篇幅限制,本例子就留給讀者朋友自行設計,相信也是不難完成的。

案例

上一節我們深入淺出的從自定義類加載器的開始引入,到實現多個類加載器加載同個類文件,最後完成舊類加載器和對象的回收,整個流程闡述了熱部署的實現細節。那麼這一節我們介紹現有實現熱部署的通用解決方案,本質就是對上文原理的實現,加上性能和設計上的優化,注意本節我們應用的只是類加載器的技術,後面章節還會介紹的字節碼層面的底層操作技術。

  • OSGI


OSGI(Open Service Gateway Initiative)是一套開發和部署應用程序的java框架。我們從官網可以看到OSGI其實是一套規範,好比Servlet定義了服務端對於處理來自網絡請求的一套規範,比如init,service,destroy的生命週期。然後我們通過實行這套規範來實現與客戶端的交互,在調用init初始化完Servlet對象後通過多線程模式使用service響應網絡請求。如果從響應模式比較我們還可以瞭解下Webflux的規範,以上兩種都是處理網絡請求的方式,當然你舉例說CGI也是一種處理網絡請求的規範,CGI採用的是多進程方式來處理網絡請求,我們暫時不對這兩種規範進行優劣評價,只是說明在處理網絡請求的場景下可以採用不同的規範來實現。

好了現在回到OSGi,有了上面的鋪墊,相信對我們理解OSGI大有幫助。我們說OSGI首先是一種規範,既然是規範我們就要看看都規範了啥,比如Servlet也是一種規範,它規範了生命週期,規定應用容器中WEB-INF/classes目錄或WEB-INF/lib目錄下的jar包纔會被Web容器處理。同樣OSGI的實現框架對管轄的Bundle下面的目錄組織和文本格式也有嚴格規範,更重要的是OSGI對模塊化架構生命週期的管理。而模塊化也不只是把系統拆分成不同的JAR包形成模塊而已,真正的模塊化必須將模塊中類的引入/導出、隱藏、依賴、版本管理貫穿到生命週期管理中去。

定義:OSGI是脫胎於(OSGI Alliance)技術聯盟由一組規範和對應子規範共同定義的JAVA動態模塊化技術。實現該規範的OSGI框架(如Apache Felix)使應用程序的模塊能夠在本地或者網絡中實現端到端的通信,目前已經發布了第7版。OSGI有很多優點諸如熱部署,類隔離,高內聚,低耦合的優勢,但同時也帶來了性能損耗,而且基於OSGI目前的規範繁多複雜,開發門檻較高。

組成:執行環境,安全層,模塊層,生命週期層,服務層,框架API

核心服務:

事件服務(Event Admin Service),

包管理服務(Package Admin Service)

日誌服務(Log Service)

配置管理服務(Configuration Admin Service)

HTTP服務(HTTP Service)

用戶管理服務(User Admin Service)

設備訪問服務(Device Access Service)

IO連接器服務(IO Connector Service)

聲明式服務(Declarative Services)

其他OSGi標準服務

JVM核心知識體系知識清單(中)

本節我們討論的核心是熱部署,所以我們不打算在這裏講解全部得OSGI技術,在上文實現熱部署後我們重點來剖析OSGI關於熱部署的機制。至於OSGI模塊化技術和java9的模塊化的對比和關聯,後面有時間會開個專題專門介紹模塊化技術。

從類加載器技術應用的角度切入我們知道OSGI規範也是打破雙親委派機制,除了框架層面需要依賴JVM默認類加載器之外,其他Bundle(OSGI定義的模塊單元)都是由各自的類加載器來加載,而OSGI框架就負責模塊生命週期,模塊交互這些核心功能,同時創建各個Bundle的類加載器,用於直接加載Bundle定義的jar包。由於打破雙親委派模式,Bundle類加載器不再是雙親委派模型中的樹狀結構,而是進一步發展爲更加複雜的網狀結構(因爲各個Bundle之間有相互依賴關係),當收到類加載請求時,OSGi將按照下面的順序進行類搜索:

1)將以java.*開頭的類委派給父類加載器加載。

2)否則,將委派列表名單內(比如sun或者javax這類核心類的包加入白名單)的類委派給父類加載器加載。

3)否則,將Import列表中的類委派給Export這個類的Bundle的類加載器加載。

4)否則,查找當前Bundle的ClassPath,使用自己的類加載器加載。

5)否則,查找類是否在自己的Fragment Bundle(OSGI框架緩存包)中,如果在,則委派給Fragment Bundle的類加載器加載。

6)否則,查找Dynamic Import列表的Bundle,委派給對應Bundle的類加載器加載。

7)否則,類查找失敗。

這一系列的類加載操作,其實跟我們上節實現的自定義類加載技術本質上是一樣的,只不過實現OSGI規範的框架需要提供模塊之間的註冊通信組件,還有模塊的生命週期管理,版本管理。OSGI也只是JVM上面運行的一個普通應用實例,只不過通過模塊內聚,版本管理,服務依賴一系列的管理,實現了模塊的即時更新,實現了熱部署。

其他熱部署解決方案多數也是利用類加載器的特點做文章,當然不止是類加載器,還會應用字節碼技術,下面我們主要簡單列舉應用類加載器實現的熱部署解決方案。

  • Groovy


Groovy兼顧動態腳本語言的功能,使用的時候無外乎也是通過GroovyClassLoader來加載腳本文件,轉爲JVM的類對象。那麼每次更新groovy腳本就可以動態更新應用,也就達到了熱部署的功能了。

Class groovyClass = classLoader.parseClass(new GroovyCodeSource(sourceFile));
GroovyObject instance = (GroovyObject)groovyClass.newInstance();//proxy
  • Clojure

JSP

JSP其實翻譯爲Servlet後也是由對應新的類加載器去加載,這跟我們上節講的流程一模一樣,所以這裏就補展開講解了。

介紹完熱部署技術,可能很多同學對熱部署的需求已經沒有那麼強烈,畢竟熱部署過程中帶來的弊端也不容忽視,比如替換舊的類加載器過程會產生大量的內存碎片,導致JVM進行高負荷的GC工作,反覆進行熱部署還會導致JVM內存不足而導致內存溢出,有時候甚至還不如直接重啓應用來得更快一點,而且隨着分佈式架構的演進和微服務的流行,應用重啓也早就實現服務編排化,配合豐富的部署策略,也可以同樣保證系統穩定持續服務,我們更多的是通過熱部署技術來深刻認識到JVM加載類的技術演進。

4.3.2類隔離

背景

先介紹一下類隔離的背景,我們費了那麼大的勁設計出類加載器,如果只是用於加載外部類字節流那就過於浪費了。通常我們的應用依賴不同的第三方類庫經常會出現不同版本的類庫,如果只是使用系統內置的類加載器的話,那麼一個類庫只能加載唯一的一個版本,想加載其他版本的時候會從緩存裏面發現已經存在而停止加載。但是我們的不同業務以來的往往是不同版本的類庫,這時候就會出現ClassNotFoundException。爲什麼只有運行的是纔會出現這個異常呢,因爲編譯的時候我們通常會使用MAVEN等編譯工具把衝突的版本排除掉。另外一種情況是WEB容器的內核依賴的第三方類庫需要跟應用依賴的第三方類庫隔離開來,避免一些安全隱患,不然如果共用的話,應用升級依賴版本就會導致WEB容器不穩定。

基於以上的介紹我們知道類隔離實在是剛需,那麼接下來介紹一下如何實現這個剛需。

原理

首先我們要了解一下原理,其實原理很簡單,真的很簡單,請允許我總結爲“唯一標識原理”。我們知道內存裏面定位類實例的座標。那麼由這兩個因子組合起來我們可以得出一種普遍的應用,用不同類加載器來加載類相同類(類全限定名一致,版本不一致)是可以實現的,也就是在JVM看來,有相同類全名的類是完全不同的兩個實例,但是在業務視角我們卻可以視爲相同的類。

JVM核心知識體系知識清單(中)

實現

原理很簡單,比如我們知道Spring容器本質就是一個生產和管理bean的集合對象,但是卻包含了大量的優秀設計模式和複雜的框架實現。同理隔離容器雖然原理很簡單,但是要實現一個高性能可擴展的高可用隔離容器,卻不是那麼簡單。我們上文談的場景是在內存運行的時候才發現問題,介紹內存隔離技術之前,我們先普及更爲通用的衝突解決方法。

衝突排除

衝突總是先發生在編譯時期,那麼基本Maven工具可以幫我們完成大部分的工作,Maven的工作模式就是將我們第三方類庫的所有依賴都依次檢索,最終排除掉產生衝突的jar包版本。

衝突適配

當我們無法通過簡單的排除來解決的時候,另外一個方法就是重新裝配第三方類庫,這裏我們要介紹一個開源工具jarjar (https://github.com/shevek/jarjar)。該工具包可以通過字節碼技術將我們依賴的第三方類庫重命名,同時修改代碼裏面對第三方類庫引用的路徑。這樣如果出現同名第三方類庫的話,通過該“硬編碼”的方式修改其中一個類庫,從而消除了衝突。

衝突隔離

上面兩種方式在小型系統比較適合,也比較敏捷高效。但是對於分佈式大型系統的話,通過硬編碼方式來解決衝突就難以完成了。辦法就是通過隔離容器,從邏輯上區分類庫的作用域,從而對內存的類進行隔離。

5.內存管理

5.1內存結構

5.1.1邏輯分區

JVM內存從應用邏輯上可分爲如下區域。

  • 程序計數器:字節碼行號指示器,每個線程需要一個程序計數器
  • 虛擬機棧:方法執行時創建棧幀(存儲局部變量,操作棧,動態鏈接,方法出口)編譯時期就能確定佔用空間大小,線程請求的棧深度超過jvm運行深度時拋StackOverflowError,當jvm棧無法申請到空閒內存時拋OutOfMemoryError,通過-Xss,-Xsx來配置初始內存
  • 本地方法棧:執行本地方法,如操作系統native接口
  • 堆:存放對象的空間,通過-Xmx,-Xms配置堆大小,當堆無法申請到內存時拋OutOfMemoryError
  • 方法區:存儲類數據,常量,常量池,靜態變量,通過MaxPermSize參數配置
  • 對象訪問:初始化一個對象,其引用存放於棧幀,對象存放於堆內存,對象包含屬性信息和該對象父類、接口等類型數據(該類型數據存儲在方法區空間,對象擁有類型數據的地址)


而實際上JVM內存分類實際上的物理分區還有更爲詳細,整體上分爲堆內存和非堆內存,具體介紹如下。

5.1.2 內存模型

堆內存

堆內存是運行時的數據區,從中分配所有java類實例和數組的內存,可以理解爲目標應用依賴的對象。堆在JVM啓動時創建,並且在應用程序運行時可能會增大或減小。可以使用-Xms 選項指定堆的大小。堆可以是固定大小或可變大小,具體取決於垃圾收集策略。可以使用-Xmx選項設置最大堆大小。默認情況下,最大堆大小設置爲64 MB。

JVM堆內存在物理上分爲兩部分:新生代和老年代。新生代是爲分配新對象而保留堆空間。當新生代佔用完時,Minor GC垃圾收集器會對新生代區域執行垃圾回收動作,其中在新生代中生活了足夠長的所有對象被遷移到老年代,從而釋放新生代空間以進行更多的對象分配。此垃圾收集稱爲 Minor GC。新生代分爲三個子區域:伊甸園Eden區和兩個倖存區S0和S1。

JVM核心知識體系知識清單(中)

關於新生代內存空間:

  • 大多數新創建的對象都位於Eden區內存空間
  • 當Eden區填滿對象時,執行Minor GC並將所有幸存對象移動到其中一個倖存區空間
  • Minor GC還會檢查倖存區對象並將其移動到其他倖存者空間,也即是倖存區總有一個是空的
  • 在多次GC後還存活的對象被移動到老年代內存空間。至於經過多少次GC晉升老年代則由參數配置,通常爲15


當老年區填滿時,老年區同樣會執行垃圾回收,老年區還包含那些經過多Minor GC後還存活的長壽對象。垃圾收集器在老年代內存中執行的回收稱爲Major GC,通常需要更長的時間。

非堆內存

JVM的堆以外內存稱爲非堆內存。也即是JVM自身預留的內存區域,包含JVM緩存空間,類結構如常量池、字段和方法數據,方法,構造方法。類非堆內存的默認最大大小爲64 MB。可以使用-XX:MaxPermSize VM選項更改此選項,非堆內存通常包含如下性質的區域空間:

  • 元空間(Metaspace)


在Java 8以上版本已經沒有Perm Gen這塊區域了,這也意味着不會再由關於“java.lang.OutOfMemoryError:PermGen”內存問題存在了。與駐留在Java堆中的Perm Gen不同,Metaspace不是堆的一部分。類元數據多數情況下都是從本地內存中分配的。默認情況下,元空間會自動增加其大小(直接又底層操作系統提供),而Perm Gen始終具有固定的上限。可以使用兩個新標誌來設置Metaspace的大小,它們是:“ - XX:MetaspaceSize ”和“ -XX:MaxMetaspaceSize ”。Metaspace背後的含義是類的生命週期及其元數據與類加載器的生命週期相匹配。也就是說,只要類加載器處於活動狀態,元數據就會在元數據空間中保持活動狀態,並且無法釋放。

  • 代碼緩存


運行Java程序時,它以分層方式執行代碼。在第一層,它使用客戶端編譯器(C1編譯器)來編譯代碼。分析數據用於服務器編譯的第二層(C2編譯器),以優化的方式編譯該代碼。默認情況下,Java 7中未啓用分層編譯,但在Java 8中啓用了分層編譯。實時(JIT)編譯器將編譯的代碼存儲在稱爲代碼緩存的區域中。它是一個保存已編譯代碼的特殊堆。如果該區域的大小超過閾值,則該區域將被刷新,並且GC不會重新定位這些對象。Java 8中已經解決了一些性能問題和編譯器未重新啓用的問題,並且在Java 7中避免這些問題的解決方案之一是將代碼緩存的大小增加到一個永遠不會達到的程度。

  • 方法區


方法區域是Perm Gen中空間的一部分,用於存儲類結構(運行時常量和靜態變量)以及方法和構造函數的代碼。

  • 內存池


內存池由JVM內存管理器創建,用於創建不可變對象池。內存池可以屬於Heap或Perm Gen,具體取決於JVM內存管理器實現。

  • 常量池


常量包含類運行時常量和靜態方法,常量池是方法區域的一部分。

  • Java堆棧內存


Java堆棧內存用於執行線程。它們包含特定於方法的特定值,以及對從該方法引用的堆中其他對象的引用。

  • Java堆內存配置項


Java提供了許多內存配置項,我們可以使用它們來設置內存大小及其比例,常用的如下:

JVM核心知識體系知識清單(中)

5.2垃圾回收

5.2.1垃圾回收策略

流程

垃圾收集是釋放堆中的空間以分配新對象的過程。垃圾收集器是JVM管理的進程,它可以查看內存中的所有對象,並找出程序任何部分未引用的對象,刪除並回收空間以分配給其他對象。通常會經過如下步驟:

  • 標記:標記哪些對象被使用,哪些已經是無法觸達的無用對象
  • 刪除:刪除無用對象並回收要分配給其他對象
  • 壓縮:性能考慮,在刪除無用的對象後,會將所有幸存對象集中移動到一起,騰出整段空間

策略

虛擬機棧、本地棧和程序計數器在編譯完畢後已經可以確定所需內存空間,程序執行完畢後也會自動釋放所有內存空間,所以不需要進行動態回收優化。JVM內存調優主要針對堆和方法區兩大區域的內存。通常對象分爲Strong、sfot、weak和phantom四種類型,強引用不會被回收,軟引用在內存達到溢出邊界時回收,弱引用在每次回收週期時回收,虛引用專門被標記爲回收對象,具體回收策略如下:

  • 對象優先在Eden區分配:
  • 新生對象回收策略Minor GC(頻繁)
  • 老年代對象回收策略Full GC/Major GC(慢)
  • 大對象直接進入老年代:超過3m的對象直接進入老年區 -XX:PretenureSizeThreshold=3145728(3M)
  • 長期存貨對象進入老年區:
  • Survivor區中的對象經歷一次Minor GC年齡增加一歲,超過15歲進入老年區
  • -XX:MaxTenuringThreshold=15
  • 動態對象年齡判定:設置Survivor區對象佔用一半空間以上的對象進入老年區

算法

垃圾收集有如下常用的算法:

  • 標記-清除
  • 複製
  • 標記-整理
  • 分代收集(新生用複製,老年用標記-整理)

5.2.2 垃圾回收器

分類

  • serial收集器:單線程,主要用於client模式
  • ParNew收集器:多線程版的serial,主要用於server模式
  • Parallel Scavenge收集器:線程可控吞吐量(用戶代碼時間/用戶代碼時間+垃圾收集時間),自動調節吞吐量,用戶新生代內存區
  • Serial Old收集器:老年版本serial
  • Parallel Old收集器:老年版本Parallel Scavenge
  • CMS(Concurrent Mark Sweep)收集器:停頓時間短,併發收集
  • G1收集器:分塊標記整理,不產生碎片

配置

  • 串行GC(-XX:+ UseSerialGC):串行GC使用簡單的標記-掃描-整理方法,用於新生代和老年代的垃圾收集,即Minor和Major GC
  • 並行GC(-XX:+ UseParallelGC):並行GC與串行GC相同,不同之處在於它爲新生代垃圾收集生成N個線程,其中N是系統中的CPU核心數。我們可以使用-XX:ParallelGCThreads = n JVM選項來控制線程數
  • 並行舊GC(-XX:+ UseParallelOldGC):這與Parallel GC相同,只是它爲新生代和老年代垃圾收集使用多個線程
  • 併發標記掃描(CMS)收集器(-XX:+ UseConcMarkSweepGC):CMS也稱爲併發低暫停收集器。它爲老年代做垃圾收集。CMS收集器嘗試通過在應用程序線程內同時執行大多數垃圾收集工作來最小化由於垃圾收集而導致的暫停。年輕一代的CMS收集器使用與並行收集器相同的算法。我們可以使用-XX限制CMS收集器中的線程數 :ParallelCMSThreads = n
  • G1垃圾收集器(-XX:+ UseG1GC):G1從長遠看要是替換CMS收集器。G1收集器是並行,併發和遞增緊湊的低暫停垃圾收集器。G1收集器不像其他收集器那樣工作,並且沒有年輕和老一代空間的概念。它將堆空間劃分爲多個大小相等的堆區域。當調用垃圾收集器時,它首先收集具有較少實時數據的區域,因此稱爲“Garbage First”也即是G1

JVM核心知識體系知識清單(上)

34張架構史上最全技術知識圖譜

相關文章