深入理解Java虛擬機(超級詳細)

1. 內存區域與內存溢出異常

Java虛擬機在執行Java程序的過程中會把他所管理的內存劃分爲若干個不同的數據區域(運行時數據區)。Java虛擬機規範將JVM所管理的內存分爲以下幾個運行時數據區:程序計數器、Java虛擬機棧、本地方法棧、Java堆、方法區。下面詳細闡述各數據區所存儲的數據類型。

在這裏插入圖片描述

1.1 程序計數器(Program Counter Register)

一塊較小的內存空間,它是當前線程所執行的字節碼的行號指示器,字節碼解釋器工作時通過改變該計數器的值來選擇下一條需要執行的字節碼指令,分支、跳轉、循環等基礎功能都要依賴它來實現。每條線程都有一個獨立的的程序計數器,各線程間的計數器互不影響,因此該區域是線程私有的

當線程在執行一個Java方法時,該計數器記錄的是正在執行的虛擬機字節碼指令的地址,當線程在執行的是Native方法(調用本地操作系統方法)時,該計數器的值爲空。另外,該內存區域是唯一一個在Java虛擬機規範中沒有規定任何OOM(內存溢出:OutOfMemoryError)情況的區域。

1.2 Java虛擬機棧(Java Virtual Machine Stacks)

虛擬機棧是一個線程執行的區域,保存着一個線程中方法的調用狀態。換句話說,一個Java線程的運行狀態,由一個虛擬機棧來保存,所以虛擬機棧肯定是線程私有的,隨着線程的創建而創建。每個方法被執行的時候都會同時創建一個棧幀,棧幀是用於支持虛擬機進行方法調用和方法執行的數據結構,即每個方法對應一個棧幀。調用一個方法,就會向棧中壓入一個棧幀;一個方法調用完成,就會把該棧幀從棧中彈出。對於執行引擎來講,活動線程中,只有棧頂的棧幀是有效的,稱爲當前棧幀,這個棧幀所關聯的方法稱爲當前方法,執行引擎所運行的所有字節碼指令都只針對當前棧幀進行操作。
在這裏插入圖片描述
在Java虛擬機規範中,對這個區域規定了兩種異常情況:

  1. 如果線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError異常。
  2. 如果虛擬機在動態擴展棧時無法申請到足夠的內存空間,則拋出OutOfMemoryError異常。

這兩種情況存在着一些互相重疊的地方:當棧空間無法繼續分配時,到底是內存太小,還是已使用的棧空間太大,其本質上只是對同一件事情的兩種描述而已。在單線程的操作中,無論是由於棧幀太大,還是虛擬機棧空間太小,當棧空間無法分配時,虛擬機拋出的都是StackOverflowError異常,而不會得到OutOfMemoryError異常。而在多線程環境下,則會拋出OutOfMemoryError異常。

棧幀

每個棧幀對應一個被調用的方法,可以理解爲一個方法的運行空間。每個棧幀中包括局部變量表(Local Variables)、操作數棧(Operand Stack)、指向運行時常量池的引用(A reference to the run-time constant pool)、方法返回地址(Return Address)和附加信息。在編譯程序代碼時,棧幀中需要多大的局部變量表、多深的操作數棧都已經完全確定了,並且寫入了方法表的Code屬性之中。因此,一個棧幀需要分配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決於具體的虛擬機實現。
在這裏插入圖片描述

  • 局部變量表
    方法中定義的局部變量以及方法的參數存放在這張表中。局部變量表中的變量不可直接使用,如需要使用的話,必須通過相關指令將其加載至操作數棧中作爲操作數使用。

  • 操作數棧
    當一個方法開始執行時,它的操作棧是空的,在方法的執行過程中,會有各種字節碼指令(比如:加操作、賦值元算等)向操作棧中寫入和提取內容,也就是入棧和出棧操作。

  • 動態鏈接
    每個棧幀都包含一個指向運行時常量池(在方法區中,後面介紹)中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程中的動態連接。Class文件的常量池中存在有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用爲參數。這些符號引用,一部分會在類加載階段或第一次使用的時候轉化爲直接引用(如final、static域等),稱爲靜態解析,另一部分將在每一次的運行期間轉化爲直接引用,這部分稱爲動態連接。

  • 方法返回地址
    當一個方法被執行後,有兩種方式退出該方法:執行引擎遇到了任意一個方法返回的字節碼指令或遇到了異常,並且該異常沒有在方法體內得到處理。無論採用何種退出方式,在方法退出之後,都需要返回到方法被調用的位置,程序才能繼續執行。方法返回時可能需要在棧幀中保存一些信息,用來幫助恢復它的上層方法的執行狀態。一般來說,方法正常退出時,調用者的PC計數器的值就可以作爲返回地址,棧幀中很可能保存了這個計數器值,而方法異常退出時,返回地址是要通過異常處理器來確定的,棧幀中一般不會保存這部分信息。

    方法退出的過程實際上等同於把當前棧幀出棧,因此退出時可能執行的操作有:恢復上層方法的局部變量表和操作數棧,如果有返回值,則把它壓入調用者棧幀的操作數棧中,調整PC計數器的值以指向方法調用指令後面的一條指令。

1.3 本地方法棧(Native Method Stacks)

該區域與虛擬機棧所發揮的作用非常相似,只是虛擬機棧爲虛擬機執行Java方法服務,而本地方法棧則爲使用到的本地操作系統(Native)方法服務。

1.4 Java堆(Java Heap)

Java Heap是Java虛擬機所管理的內存中最大的一塊,它是所有線程共享的一塊內存區域。幾乎所有的對象實例和數組都在這類分配內存。Java Heap是垃圾收集器管理的主要區域,因此很多時候也被稱爲“GC堆”。內部會劃分出多個線程私有的分配緩衝區(Thread Local Allocation Buffer, TLAB)。可以位於物理上不連續的空間,但是邏輯上要連續。

根據Java虛擬機規範的規定,Java堆可以處在物理上不連續的內存空間中,只要邏輯上是連續的即可。如果在堆中沒有內存可分配時,並且堆也無法擴展時,將會拋出OutOfMemoryError異常。

堆空間分爲老年代和年輕代。剛創建的對象存放在年輕代,而老年代中存放生命週期長久的實例對象。年輕代中又被分爲Eden區和兩個Survivor區(From Space和To Space)。新的對象分配是首先放在Eden區,Survivor區作爲Eden區和Old區的緩衝,在Survivor區的對象經歷若干次GC仍然存活的,就會被轉移到老年代。當一個對象大於eden區而小於old區(老年代)的時候會直接扔到old區。 而當對象大於old區時,會直接拋出OutOfMemoryError(OOM)

1.5 方法區(Method Area)

方法區是各個線程共享的內存區域,在虛擬機啓動時創建,雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分,但是它卻又一個別名叫做Non-Heap(非堆),目的是與Java堆區分開來,方法區域又被稱爲“永久代”,但這僅僅對於Sun HotSpot來講,JRockit和IBM J9虛擬機中並不存在永久代的概念。用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。相對而言,垃圾收集行爲在這個區域比較少出現。該區域的內存回收目標主要針是對廢棄常量的和無用類的回收。運行時常量池是方法區的一部分,Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Class文件常量池),用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載後存放到方法區的運行時常量池中。運行時常量池相對於Class文件常量池的另一個重要特徵是具備動態性,Java語言並不要求常量一定只能在編譯期產生,也就是並非預置入Class文件中的常量池的內容才能進入方法區的運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的是String類的intern()方法。

根據Java虛擬機規範的規定,當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。

1.6 直接內存(Direct Memory)

直接內存並不是虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域,它直接從操作系統中分配,因此不受Java堆大小的限制,但是會受到本機總內存的大小及處理器尋址空間的限制,因此它也可能導致OutOfMemoryError異常出現。在JDK1.4中新引入了NIO機制,它是一種基於通道與緩衝區的新I/O方式,可以直接從操作系統中分配直接內存,即在堆外分配內存,這樣能在一些場景中提高性能,因爲避免了在Java堆和Native堆中來回複製數據。
在這裏插入圖片描述

2. HotSpot 虛擬機對象

2.1 對象的內存佈局

在 HotSpot 虛擬機中,分爲 3 塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)

  • 對象頭

包含兩部分,第一部分用於存儲對象自身的運行時數據,如哈希碼、GC 分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程 ID、偏向時間戳等,32 位虛擬機佔 32 bit,64 位虛擬機佔 64 bit。官方稱爲 ‘Mark Word’。第二部分是類型指針,即對象指向它的類的元數據指針,虛擬機通過這個指針確定這個對象是哪個類的實例。另外,如果是 Java 數組,對象頭中還必須有一塊用於記錄數組長度的數據,因爲普通對象可以通過 Java 對象元數據確定大小,而數組對象不可以。

  • 實例數據

程序代碼中所定義的各種類型的字段內容(包含父類繼承下來的和子類中定義的)。

  • 對齊填充

不是必然需要,主要是佔位,保證對象大小是某個字節的整數倍。

2.2 對象的創建

  1. 遇到 new 指令時,首先檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被加載、解析和初始化過。如果沒有,執行相應的類加載
  2. 類加載檢查通過之後,爲新對象分配內存(內存大小在類加載完成後便可確認)。在堆的空閒內存中劃分一塊區域(‘指針碰撞-內存規整’或‘空閒列表-內存交錯’的分配方式)
  3. 內存空間分配完成後會初始化爲 0(不包括對象頭),接下來就是填充對象頭,把對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的 GC 分代年齡等信息存入對象頭。
  4. 執行 new 指令後執行 init 方法後纔算一份真正可用的對象創建完成。

2.3 對象的訪問定位

由於reference類型在Java虛擬機規範裏面只規定了一個指向對象的引用,並沒有定義這個引用應該通過哪種方式去定位,以及訪問到Java堆中的對象的具體位置,因此不同虛擬機實現的對象訪問方式會有所不同,主流的訪問方式有兩種:使用句柄池和直接使用指針。

  • 通過句柄訪問
    Java 堆中會分配一塊內存作爲句柄池。reference 存儲的是句柄地址
    在這裏插入圖片描述

  • 使用直接指針訪問
    Java 堆中會分配一塊內存作爲句柄池。reference 存儲的是句柄地址
    在這裏插入圖片描述
    比較:使用句柄的最大好處是 reference 中存儲的是穩定的句柄地址,在對象移動(垃圾收集時移動對象是非常普遍的行爲)是隻改變實例數據指針地址,reference 自身不需要修改。直接指針訪問的最大好處是速度快,節省了一次指針定位的時間開銷。如果是對象頻繁 GC 那麼句柄方法好,如果是對象頻繁訪問則直接指針訪問好。

3. JVM垃圾回收機制

3.1 概述

  • 程序計數器、虛擬機棧、本地方法棧 3 個區域隨線程生滅(因爲是線程私有),棧中的棧幀隨着方法的進入和退出而有條不紊地執行着出棧和入棧操作。而 Java 堆和方法區則不一樣,一個接口中的多個實現類需要的內存可能不一樣,一個方法中的多個分支需要的內存也可能不一樣,我們只有在程序處於運行期才知道那些對象會創建,這部分內存的分配和回收都是動態的,垃圾回收期所關注的就是這部分內存。

3.2 判斷對象已死

在進行內存回收之前要做的事情就是判斷那些對象是‘死’的,哪些是‘活’的。

  • 引用計數法
    給對象添加一個引用計數器。但是難以解決循環引用問題。
    在這裏插入圖片描述
    從圖中可以看出,如果不下小心直接把 Obj1-reference 和 Obj2-reference 置 null。則在 Java 堆當中的兩塊內存依然保持着互相引用無法回收。

  • 可達性分析算法
    通過一系列的 ‘GC Roots’ 的對象作爲起始點,從這些節點出發所走過的路徑稱爲引用鏈。當一個對象到 GC Roots 沒有任何引用鏈相連的時候說明對象不可用。
    在這裏插入圖片描述

可作爲 GC Roots 的對象:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中 JNI(即一般說的 Native 方法) 引用的對象

3.3 Java 中的引用

不同的對象引用類型, GC會採用不同的方法進行回收,JVM對象的引用分爲了四種類型:

  • 強引用

類似於 Object obj = new Object(); 創建的,只要強引用在就不回收。

  • 軟引用

SoftReference 類實現軟引用。在系統要發生內存溢出異常之前,將會把這些對象列進回收範圍之中進行二次回收。

  • 弱引用

WeakReference 類實現弱引用。對象只能生存到下一次垃圾收集之前。在垃圾收集器工作時,無論內存是否足夠都會回收掉只被弱引用關聯的對象。

  • 虛引用

PhantomReference 類實現虛引用。無法通過虛引用獲取一個對象的實例,爲一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知。

無論引用計數算法還是可達性分析算法都是基於強引用而言的。

3.4 生存還是死亡

即使在可達性分析算法中不可達的對象,也並非是非死不可的,這時候它們暫時出於“緩刑”階段,一個對象的真正死亡至少要經歷兩次標記過程:
如果對象在進行中可達性分析後發現沒有與 GC Roots 相連接的引用鏈,那他將會被第一次標記並且進行一次篩選,篩選條件是此對象是否有必要執行 finalize() 方法。當對象沒有覆蓋 finalize() 方法,或者 finalize() 方法已經被虛擬機調用過,虛擬機將這兩種情況都視爲“沒有必要執行”。

如果這個對象被判定爲有必要執行 finalize() 方法,那麼這個對象竟會放置在一個叫做 F-Queue 的隊列中,並在稍後由一個由虛擬機自動建立的、低優先級的 Finalizer 線程去執行它。這裏所謂的“執行”是指虛擬機會出發這個方法,並不承諾或等待他運行結束。finalize() 方法是對象逃脫死亡命運的最後一次機會,稍後 GC 將對 F-Queue 中的對象進行第二次小規模的標記,如果對象要在 finalize() 中成功拯救自己 —— 只要重新與引用鏈上的任何一個對象簡歷關聯即可。

finalize() 方法只會被系統自動調用一次。

3.5 回收方法區

在堆中,尤其是在新生代中,一次垃圾回收一般可以回收 70% ~ 95% 的空間,而永久代的垃圾收集效率遠低於此。
永久代(方法區)垃圾回收主要兩部分內容:廢棄的常量和無用的類。

  • 判斷廢棄常量:一般是判斷沒有該常量的引用。
  • 判斷無用的類:要以下三個條件都滿足
    • 該類所有的實例都已經回收,也就是 Java 堆中不存在該類的任何實例
    • 加載該類的 ClassLoader 已經被回收
    • 該類對應的 java.lang.Class 對象沒有任何地方被引用,無法在任何地方通過反射訪問該類的方法

3.6 垃圾回收算法、

  • 標記-清除(Mark-Sweep)算法
  • 標記—清除算法是最基礎的收集算法,它分爲“標記”和“清除”兩個階段:首先標記出所需回收的對象,在標記完成後統一回收掉所有被標記的對象,它的標記過程其實就是前面的可達性分析算法中判定垃圾對象的標記過程。標記—清除算法的執行情況如下圖所示:
    在這裏插入圖片描述

主要缺點:

  • 一個是效率問題,標記和清除過程的效率都不高。
  • 另一個是空間問題,標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致:當程序在以後的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前出發另一次垃圾收集動作。
  • 複製算法(Copying)
  • 爲了解決Mark-Sweep算法的缺陷,Copying算法就被提了出來。它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用的內存空間一次清理掉,這樣一來就不容易出現內存碎片的問題。
    在這裏插入圖片描述
    這種算法雖然實現簡單,運行高效且不容易產生內存碎片,但是卻對內存空間的使用做出了高昂的代價,因爲能夠使用的內存縮減到原來的一半。很顯然,Copying算法的效率跟存活對象的數目多少有很大的關係,如果存活對象很多,那麼Copying算法的效率將會大大降低。
  • 標記-整理(Mark-Compact)算法
  • 爲了解決Copying算法的缺陷,充分利用內存空間,提出了Mark-Compact算法。該算法標記階段和Mark-Sweep一樣,但是在完成標記之後,它不是直接清理可回收對象,而是將存活對象都向一端移動,然後清理掉端邊界以外的內存。
    在這裏插入圖片描述
  • 分代收集(Generational Collection)算法
  • 當前商業虛擬機的垃圾收集 都採用分代收集,它根據對象的存活週期的不同將內存劃分爲幾塊,一般是把Java堆分爲新生代和老年代。在新生代中,每次垃圾收集時都會發現有大量對象死去,只有少量存活,因此可選用複製算法來完成收集,而老年代中因爲對象存活率高、沒有額外空間對它進行分配擔保,就必須使用標記—清除算法或標記—整理算法來進行回收。

年輕代的垃圾收集算法:

  • 在年輕代中jvm使用的是複製算法,年輕代分三個區。一個Eden區,兩個 Survivor區(一般而言)。大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被複制到Survivor區(兩個中的一個),當這個 Survivor區滿時,此區的存活對象將被複制到另外一個Survivor區,當另外一個Survivor區也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的對象,將被複制到“年老區(Tenured)”。需要注意,Survivor的兩個區是對稱的,沒先後關係,所以同一個區中可能同時存在從Eden複製過來對象,和從前一個Survivor複製過來的對象,而複製到年老區的只有從第一個Survivor區過來的對象。而且,Survivor區總有一個是空的。當survivor1區不足以存放 eden和survivor0的存活對象時,就將存活對象直接存放到老年代。

老年代(Old Generation)的回收算法:

  • 老年代的特點是每次回收都只回收少量對象,一般使用的是Mark-Compact(標記-整理)算法。在年輕代中經歷了N次垃圾回收後仍然存活的對象,就會被放到年老代中。因此,可以認爲年老代中存放的都是一些生命週期較長的對象。內存比新生代也大很多(大概比例是1:2),當老年代內存滿時觸發Major GC或Full GC,Full GC發生頻率比較低,老年代對象存活時間比較長,存活率標記高。

3.8 垃圾回收的時機

  • 當Eden區或者Servior區不夠用了
  • 老年代空間不夠用了
  • 方法區空間不夠用了
  • 手動回收,System.gc()(不建議使用)

3.9 垃圾回收分析

內存分配策略:

  • 對象優先在Eden分配。
  • 大對象直接進入老年代。
  • 長期存活的對象將進入老年代。

對垃圾回收策略說明以下兩點:

  • 新生代GC(Minor GC):發生在新生代的垃圾收集動作,因爲Java對象大多都具有朝生夕滅的特性,因此Minor GC非常頻繁,一般回收速度也比較快。
  • 老年代GC(Major GC/Full GC):發生在老年代的GC,出現了Major GC,經常會伴隨至少一次Minor GC。由於老年代中的對象生命週期比較長,因此Major GC並不頻繁,一般都是等待老年代滿了後才進行Full GC,而且其速度一般會比Minor GC慢10倍以上。另外,如果分配了Direct Memory,在老年代中進行Full GC時,會順便清理掉Direct Memory中的廢棄對象。

3.10 垃圾回收器

垃圾收集算法是內存回收的理論,而垃圾回收器是內存回收的實踐。
在這裏插入圖片描述

  • Serial 收集器

這是一個新生代單線程收集器,標記和清理都是單線程。意味着它只會使用一個 CPU 或一條收集線程去完成收集工作,並且在進行垃圾回收時必須暫停其它所有的工作線程直到收集結束。

在這裏插入圖片描述

  • Serial Old

老年代單線程收集器,Serial收集器的老年代版本,採用標記整理算法。

  • ParNew 收集器

新生代收集器,可以認爲是Serial收集器的多線程版本,使用多個線程進行垃圾收集,在多核CPU環境下有着比Serial更好的表現。是Server模式下的虛擬機首選的新生代收集器,其中有一個很重要的和性能無關的原因是,除了Serial收集器外,目前只有它能與CMS收集器配合工作。
在這裏插入圖片描述

在學習其他的垃圾收集器之前,要先明白兩個概念

  1. 並行:Parallel

指多條垃圾收集線程並行工作,此時用戶線程處於等待狀態

  1. 併發:Concurrent

指用戶線程和垃圾回收線程同時執行(不一定是並行,有可能是交叉執行),用戶進程在運行,而垃圾回收線程在另一個 CPU 上運行。

  • Parallel Scavenge收集器
  • Parallel Scavenge收集器是一個新生代收集器,它也是使用複製算法的收集器,又是並行的多線程收集器,看上去和ParNew一樣,但是Parallel Scanvenge更關注系統的吞吐量
    吞吐量=運行用戶代碼的時間/(運行用戶代碼的時間+垃圾收集時間)
    比如虛擬機總共運行了100分鐘,垃圾收集時間用了1分鐘,吞吐量=(100-1)/100=99%。
    若吞吐量越大,意味着垃圾收集的時間越短,則用戶代碼可以充分利用CPU資源,儘快完成程序的運算任務。
  • Parallel Old收集器

Parallel Scavenge收集器的老年代版本,並行收集器,吞吐量優先。使用多線程和標記-整理(Mark-Compact)算法。

在這裏插入圖片描述

  • CMS(Concurrent Mark Sweep)收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。它是一種併發收集器,採用的是Mark-Sweep算法

採用的是"標記-清除算法",整個過程分爲4步

  1. 初始標記,標記GCRoots能直接關聯到的對象,時間很短。所以這裏用的是單線程。

  2. 併發標記,進行GCRoots Tracing(可達性分析)過程,時間很長。所以用的是多線程。

  3. 重新標記,修正併發標記期間的變動部分,時間較長。

  4. 併發清除,回收內存空間,時間很長。

由於整個過程中,併發標記和併發清除,收集器線程可以與用戶線程一起工作,所以總體上來說,CMS收集器的內存回收過程是與用戶線程一起併發地執行的。
在這裏插入圖片描述
優點:併發收集、低停頓
缺點

  • 對CPU資源非常敏感,可能會導致應用程序變慢,吞吐率下降。
  • 無法處理浮動垃圾,因爲在併發清理階段用戶線程還在運行,自然就會產生新的垃圾,而在此次收集中無法收集他們,只能留到下次收
    集,這部分垃圾爲浮動垃圾,同時,由於用戶線程併發執行,所以需要預留一部分老年代空間提供併發收集時程序運行使用。
  • 由於採用的標記 - 清除算法,會產生大量的內存碎片
  • G1
    G1收集器是當今收集器技術發展最前沿的成果,它是一款面向服務端應用的收集器,它能充分利用多CPU、多核環境。因此它是一款並行與併發收集器,並且它能建立可預測的停頓時間模型。
    在這裏插入圖片描述

G1收集器有以下特點:

(1). 並行和併發。使用多個CPU來縮短Stop The World停頓時間,與用戶線程併發執行。

(2). 分代收集。獨立管理整個堆,但是能夠採用不同的方式去處理新創建對象和已經存活了一段時間、熬過多次GC的舊對象,以獲取更好的收集效果。

(3). 空間整合。基於標記 - 整理算法,無內存碎片產生。

(4). 可預測的停頓。能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度爲M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒,那麼就會回收在不超過N毫秒時間內能夠回收的,可能還有不能回收的。

使用G1收集器時,Java堆的內存佈局與其他收集器有很大差別,它將整個Java堆劃分爲多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分(可以不連續)Region的集合。

參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章