JVM 內(nèi)存架構(gòu)和 GC 算法基礎(chǔ)
目的
本文討論了 JDK8 及更高版本的堆內(nèi)存和棧內(nèi)存管理的基本概念。以及 GC 及其算法的基礎(chǔ)知識。
內(nèi)存管理的重要性
Java 垃圾收集器不能確保堆內(nèi)存完全空閑,而且對于開發(fā)人員來說,不可能強制垃圾收集器在特定時間運行。因此,了解 Java 中的內(nèi)存管理是如何工作的對開發(fā)程序會很有幫助。
了解內(nèi)存管理有助于編寫優(yōu)化的內(nèi)存效率代碼,并有助于避免程序中任何與內(nèi)存相關(guān)的問題,這些問題可能導(dǎo)致應(yīng)用程序運行緩慢,并有助于避免 StackOverFlowError 和 OutOfMemoryError 等錯誤。
棧內(nèi)存
棧是一種線性數(shù)據(jù)結(jié)構(gòu),是 Java 分配的靜態(tài)內(nèi)存,用于存儲堆對象引用,也存儲 Java 原始類型值。棧以后進先出 (LIFO) 順序訪問內(nèi)存,并且棧比堆內(nèi)存快。
每個線程在內(nèi)存中創(chuàng)建自己的棧,這反過來又使棧內(nèi)存線程安全。
Java 中的方法僅訪問方法體(方法范圍內(nèi))內(nèi)的棧內(nèi)存中的對象。當(dāng)方法執(zhí)行完成時,該方法對應(yīng)的塊會從棧中清除。
在上面的程序中,我們可以看到,當(dāng)控件到達main方法時,棧中會有一個args的入口。然后當(dāng)控件在下一行時,一個新條目被添加到棧中。
當(dāng)控件超出方法的范圍時,引用將從棧中刪除。
如果棧內(nèi)存已滿,JVM 會拋出 StackOverFlowError。
堆內(nèi)存
堆用于JVM在運行時為Java對象動態(tài)分配內(nèi)存。任何新對象都存儲在堆中,并且對象的引用(示例變量)存儲在棧中。您可以在下面的示例中看到示例代碼的變量如何存儲在堆和堆棧中。
下面是上述代碼片段在堆中的內(nèi)存分配。
堆內(nèi)存可以分解成更小的部分,稱為代,它們是年輕代、年老/終身代和永久代。
年輕一代
所有新對象都在此內(nèi)存段中分配。 年輕代由 Eden 和兩個 Survivor 空間組成。當(dāng) Eden 填滿時,垃圾收集發(fā)生在年輕代上,這稱為 Minor GC。在 Minor GC 期間,來自年輕代的引用對象被移動到 Survivor 空間 #1,并且對象的年齡增加。
例如,在下圖中,“對象 1”和“對象 2”將在第一次 Minor GC 之后移動到 Survivor 空間 #1,并且它們將具有指定的年齡。如果“對象 1”在第一次 Minor GC 中幸存下來,則年齡為零?,F(xiàn)在如果“對象 1”在下一次 Minor GC 中也幸存下來,那么它將被移動到幸存者空間 2,并且年齡將再次增加。
在第二次 Minor GC 期間,駐留在 Survivor 空間 #1 中的對象(具有引用)將被移動到 Survivor #2,并且年齡將增加(即根據(jù)示例年齡將從零變?yōu)橐?。并且從完整的年輕代空間中所有未引用的對象都將被刪除。
老一代
老年代是存放長壽命對象(最老的對象)的地方。 年輕代對象有年齡的上限或閾值。 一旦對象達到該上限,則該對象將移至老一代或終身代。
終身代
這部分堆內(nèi)存用于存儲運行時類和方法的元數(shù)據(jù)。 從 JDK 8 開始,這部分內(nèi)存已被 Java 完全刪除,并被 Metaspace 概念所取代。您仍然可以設(shè)置 --XX:PermSize 和 -XX:MaxPermSize 配置。但是,如果您在 JDK 8 或更高版本上運行應(yīng)用程序,則會在運行時收到警告。
元空間
這是從 JDK 8 版本開始引入的,它是一個可調(diào)整大小的內(nèi)存區(qū)域并從本機內(nèi)存中分配。元空間保存類元數(shù)據(jù),它不是一個連續(xù)的內(nèi)存位置。
每當(dāng) Metaspace 達到為 Metaspace 分配的最大大小時,Java 就會觸發(fā)自動 GC 以釋放 Metaspace 內(nèi)存。
元空間選項是 -XX:MetaspaceSize=size 和 -XX:MaxMetaspaceSize=size
垃圾收集
Java程序編譯并更改為字節(jié)碼并在JVM(Java虛擬機)上運行。Java 程序的對象是在該程序的專用堆內(nèi)存上創(chuàng)建的。隨著時間的推移,會創(chuàng)建更多對象,并且程序不再需要一些對象(未引用和取消范圍)。垃圾回收是 Java 執(zhí)行自動內(nèi)存管理并通過刪除未引用對象來釋放內(nèi)存空間的過程。
JVM 結(jié)合了不同的垃圾收集算法。垃圾收集算法檢查內(nèi)存中的每個引用對象,其余對象被視為垃圾收集。
GC算法的類型
以下是 JVM 可用的 4 種類型的 GC 算法。
- 并行GC
- 串行GC
- 并發(fā)標(biāo)記和掃描
- G1 垃圾優(yōu)先
并行GC
專為具有中等或大量數(shù)據(jù)的多線程應(yīng)用程序而設(shè)計,在多處理器環(huán)境中運行良好。但它會在垃圾收集期間凍結(jié)所有應(yīng)用程序線程。 JVM 選項是 -XX:+UseParallelGC ,您可以選擇使用 -XX:ParallelGCThreads= 設(shè)置并行線程數(shù)。
串行GC
主要設(shè)計用于單線程環(huán)境。 Liek Parallel GC,它還會在垃圾收集期間凍結(jié)所有應(yīng)用程序線程。JVM 選項是 -XX:+UseSerialGC。
并發(fā)標(biāo)記和滲漏(CMS)
這是一個并發(fā) GC,旨在縮短 GC 暫停時間,并且不需要停止正在運行的應(yīng)用程序來執(zhí)行 GC。這就是為什么這個過程比串行或并行 GC 慢的原因。
它使用多線程進行垃圾收集,并且可以與垃圾收集器共享處理器資源。JVM 選項是 -XX:+UseConcMarkSweepGC。
G1 垃圾收集器(G1GC)
這是另一種最高效的并發(fā) GC,專為具有大量內(nèi)存的多處理器環(huán)境而設(shè)計。JVM 選項是 -XX:+UseG1GC。
選擇 GC 算法的參數(shù)
除非您對 GC 時間有特定要求并且需要放置其他規(guī)范,否則最好讓 JVM 自己選擇 GC 算法。
如果要選擇和配置 GC 算法,那么需要考慮的參數(shù)很少,如堆大小、CPU 核心數(shù)、應(yīng)用程序數(shù)據(jù)集體積、吞吐量、暫停時間、延遲。
a、堆大小 - 分配給 JVM 的內(nèi)存總量。更大的堆大小意味著 GC 將花費更多時間。更大的堆內(nèi)存意味著與更少的堆內(nèi)存相比,JVM 觸發(fā) GC 的頻率不會那么頻繁。JVM 選項是 -Xms=和 -Xmx=,其中 -Xms 表示最小值,-Xmx 是最大值。
b、CPU 核心 - GC 算法因 CPU 核心數(shù)量而異。其中一些是為單核 CPU 設(shè)計的,一些是為多核 CPU 設(shè)計的。
c、應(yīng)用程序數(shù)據(jù)集 - 這是指應(yīng)用程序使用的對象數(shù)量。創(chuàng)建更多數(shù)量的新對象,導(dǎo)致填充年輕代空間,需要更多的 GC 時間來釋放內(nèi)存。
d、吞吐量 - 它是完成應(yīng)用程序任務(wù)所需的總時間(GC 外)的百分比。它與分配給 JVM 的內(nèi)存成反比。
e、暫停時間 - GC 算法在內(nèi)存回收期間停止應(yīng)用程序所花費的時間。它根據(jù)不同的GC算法而有所不同。JVM 選項是 -XX:MaxGCPauseMillis=
f、延遲 - 它是應(yīng)用程序的響應(yīng)時間,直接取決于 GC 暫停時間。
根據(jù)上述參數(shù),您必須選擇最適合您的應(yīng)用的 GC 算法。例如:
- ·如果應(yīng)用程序很小并且使用較小的數(shù)據(jù)集并且在沒有暫停時間要求的單處理器上運行,則串行 GC。
- 如果應(yīng)用程序性能是最高優(yōu)先級,則并行 GC。
- ·當(dāng)應(yīng)用程序的響應(yīng)時間很重要時,G1GC 或 CMS 因為它在運行 GC 時不會保留應(yīng)用程序。
原文鏈接:https://dzone.com/articles/jvm-memory-architecture-and-gc