天天看點

深入了解JVM-GC

1.GC簡介

GC(Garbage Collection):即垃圾回收器,誕生于1960年MIT的Lisp語言,主要是用來回收,釋放垃圾占用的空間。

java GC泛指java的垃圾回收機制,該機制是java與C/C++的主要差別之一,我們在日常寫java代碼的時候,一般都不需要編寫記憶體回收或者垃圾清理的代碼,也不需要像C/C++那樣做類似delete/free的操作。

1.1.為什麼需要學習GC

對象的記憶體配置設定在java虛拟機的自動記憶體配置設定機制下,一般不容易出現記憶體洩漏問題。但是寫代碼難免會遇到一些特殊情況,比如OOM神馬的。。盡管虛拟機記憶體的動态配置設定與記憶體回收技術很成熟,可萬一出現了這樣那樣的記憶體溢出問題,那麼将難以定位錯誤的原因所在。

對于本人來說,由于水準有限,而且作為小開發,并沒必要深入到GC的底層實作,但至少想要說學會看懂gc及定位一些記憶體洩漏問題。

從三個角度切入來學習GC

1.哪些記憶體要回收

2.什麼時候回收

3.怎麼回收

哪些記憶體要回收

java記憶體模型中分為五大區域已經有所了解。我們知道

程式計數器

虛拟機棧

本地方法棧

,由線程而生,随線程而滅,其中棧中的棧幀随着方法的進入順序的執行的入棧和出棧的操作,一個棧幀需要配置設定多少記憶體取決于具體的虛拟機實作并且在編譯期間即确定下來【忽略JIT編譯器做的優化,基本當成編譯期間可知】,當方法或線程執行完畢後,記憶體就随着回收,是以無需關心。

Java堆

方法區

則不一樣。方法區存放着類加載資訊,但是一個接口中多個實作類需要的記憶體可能不太一樣,一個方法中多個分支需要的記憶體也可能不一樣【隻有在運作期間才可知道這個方法建立了哪些對象沒需要多少記憶體】,這部分記憶體的配置設定和回收都是動态的,gc關注的也正是這部分的記憶體。
Java堆是GC回收的“重點區域”。堆中基本存放着所有對象執行個體,gc進行回收前,第一件事就是确認哪些對象存活,哪些死去[即不可能再被引用]
           

1.2 堆的回收區域

為了高效的回收,jvm将堆分為三個區域
1.新生代(Young Generation)NewSize和MaxNewSize分别可以控制年輕代的初始大小和最大的大小
2.老年代(Old Generation)
3.永久代(Permanent Generation)【1.8以後采用元空間,就不在堆中了】
           

1.3判斷對象是否存活算法

1.引用計數算法
早期判斷對象是否存活大多都是以這種算法,這種算法判斷很簡單,簡單來說就是給對象添加一個引用計數器,每當對象被引用一次就加1,引用失效時就減1。當為0的時候就判斷對象不會再被引用。
優點:實作簡單效率高,被廣泛使用與如python何遊戲腳本語言上。
缺點:難以解決循環引用的問題,就是假如兩個對象互相引用已經不會再被其它其它引用,導緻一直不會為0就無法進行回收。

2.可達性分析算法
目前主流的商用語言[如java、c#]采用的是可達性分析算法判斷對象是否存活。這個算法有效解決了循環利用的弊端。
它的基本思路是通過一個稱為“GC Roots”的對象為起始點,搜尋所經過的路徑稱為引用鍊,當一個對象到GC Roots沒有任何引用跟它連接配接則證明對象是不可用的。
           
深入了解JVM-GC

gc.png

可作為GC Roots的對象有四種

①虛拟機棧(棧桢中的本地變量表)中的引用的對象,就是平時所指的java對象,存放在堆中。
②方法區中的類靜态屬性引用的對象,一般指被static修飾引用的對象,加載類的時候就加載到記憶體中。
③方法區中的常量引用的對象,
④本地方法棧中JNI(native方法)引用的對象
           

即使可達性算法中不可達的對象,也不是一定要馬上被回收,還有可能被搶救一下。網上例子很多,基本上和深入了解JVM一書講的一樣對象的生存還是死亡

要真正宣告對象死亡需經過兩個過程。
1.可達性分析後沒有發現引用鍊
2.檢視對象是否有finalize方法,如果有重寫且在方法内完成自救[比如再建立引用],還是可以搶救一下,注意這邊一個類的finalize隻執行一次,這就會出現一樣的代碼第一次自救成功第二次失敗的情況。[如果類重寫finalize且還沒調用過,會将這個對象放到一個叫做F-Queue的序列裡,這邊finalize不承諾一定會執行,這麼做是因為如果裡面死循環的話可能會時F-Queue隊列處于等待,嚴重會導緻記憶體崩潰,這是我們不希望看到的。]
           

2 垃圾收集算法

jvm中,可達性分析算法幫我們解決了哪些對象可以回收的問題,垃圾收集算法則關心怎麼回收。

2.1 三大垃圾收集算法

1.标記/清除算法【最基礎】
2.複制算法
3.标記/整理算法
jvm采用`分代收集算法`對不同區域采用不同的回收算法。
           

新生代采用複制算法

新生代中因為對象都是"朝生夕死的",【深入了解JVM虛拟機上說98%的對象,不知道是不是這麼多,總之就是存活率很低】,适用于複制算法【複制算法比較适合用于存活率低的記憶體區域】。它優化了标記/清除算法的效率和記憶體碎片問題,且JVM不以5:5配置設定記憶體【由于存活率低,不需要複制保留那麼大的區域造成空間上的浪費,是以不需要按1:1【原有區域:保留白間】劃分記憶體區域,而是将記憶體分為一塊Eden空間和From Survivor、To Survivor【保留白間】,三者預設比例為8:1:1,優先使用Eden區,若Eden區滿,則将對象複制到第二塊記憶體區上。但是不能保證每次回收都隻有不多于10%的對象存貨,是以Survivor區不夠的話,則會依賴老年代年存進行配置設定】。

GC開始時,對象隻會存于Eden和From Survivor區域,To Survivor【保留白間】為空。

GC進行時,Eden區所有存活的對象都被複制到To Survivor區,而From Survivor區中,仍存活的對象會根據它們的年齡值決定去向,年齡值達到年齡門檻值(預設15是因為對象頭中年齡戰4bit,新生代每熬過一次垃圾回收,年齡+1),則移到老年代,沒有達到則複制到To Survivor。

老年代采用

标記/清除算法

标記/整理算法

由于老年代存活率高,沒有額外空間給他做擔保,必須使用這兩種算法。

2.2 枚舉根節點算法

GC Roots

被虛拟機用來判斷對象是否存活

可作為GC Roos的節點主要是在一些全局引用【如常量或靜态屬性】、執行上下文【如棧幀中本地變量表】中。那麼如何在這麼多全局變量和本地變量表找到【枚舉】根節點将是個問題。

可達性分析算法需考慮

1.如果方法區幾百兆,一個個檢查裡面的引用,将耗費大量資源。

2.在分析時,需保證這個對象引用關系不再變化,否則結果将不準确。【是以GC進行時需停掉其它所有java執行線程(Sun把這種行為稱為‘Stop the World’),即使是号稱幾乎不會停頓的CMS收集器,枚舉根節點時也需停掉線程】

解決辦法:實際上當系統停下來後JVM不需要一個個檢查引用,而是通過OopMap資料結構【HotSpot的叫法】來标記對象引用。

虛拟機先得知哪些地方存放對象的引用,在類加載完時。HotSpot把對象内什麼偏移量什麼類型的資料算出來,在jit編譯過程中,也會在特定位置記錄下棧和寄存器哪些位置是引用,這樣GC在掃描時就可以知道這些資訊。【目前主流JVM使用準确式GC】

OopMap可以幫助HotSpot快速且準确完成GC Roots枚舉以及确定相關資訊。但是也存在一個問題,可能導緻引用關系變化。

這個時候有個safepoint(安全點)的概念。

HotSpot中GC不是在任意位置都可以進入,而隻能在safepoint處進入。 GC時對一個Java線程來說,它要麼處在safepoint,要麼不在safepoint。

safepoint不能太少,否則GC等待的時間會很久

safepoint不能太多,否則将增加運作GC的負擔

安全點主要存放的位置

1:循環的末尾 
2:方法臨傳回前/調用方法的call指令後 
3:可能抛異常的位置
           

3.垃圾收集器

如果說垃圾回收算法是記憶體回收的方法論,那麼垃圾收集器就是具體實作。jvm會結合針對不同的場景及使用者的配置使用不同的收集器。
           
年輕代收集器
Serial、ParNew、Parallel Scavenge
老年代收集器
Serial Old、Parallel Old、CMS收集器
特殊收集器
G1收集器[新型,不在年輕、老年代範疇内]
           
深入了解JVM-GC

收集器,連線代表可結合使用

新生代收集器

3.1 Serial

最基本、發展最久的收集器,在jdk3以前是gc收集器的唯一選擇,Serial是單線程收集器,Serial收集器隻能使用一條線程進行收集工作,在收集的時候必須得停掉其它線程,等待收集工作完成其它線程才可以繼續工作。

雖然Serial看起來很坑,需停掉别的線程以完成自己的gc工作,但是也不是完全沒用的,比如說Serial在運作在Client模式下優于其它收集器[簡單高效,不過一般都是用Server模式,64bit的jvm甚至沒Client模式]
           

JVM的Client模式與Server模式

優點:對于Client模式下的jvm來說是個好的選擇。适用于單核CPU【現在基本都是多核了】

缺點:收集時要暫停其它線程,有點浪費資源,多核下顯得。

3.2 ParNew收集器

可以認為是Serial的更新版,因為它支援多線程[GC線程],而且收集算法、Stop The World、回收政策和Serial一樣,就是可以有多個GC線程并發運作,它是HotSpot第一個真正意義實作并發的收集器。預設開啟線程數和目前cpu數量相同【幾核就是幾個,超線程cpu的話就不清楚了 - -】,如果cpu核數很多不想用那麼多,可以通過-XX:ParallelGCThreads來控制垃圾收集線程的數量。

優點:
1.支援多線程,多核CPU下可以充分的利用CPU資源
2.運作在Server模式下新生代首選的收集器【重點是因為新生代的這幾個收集器隻有它和Serial可以配合CMS收集器一起使用】

缺點: 在單核下表現不會比Serial好,由于在單核能利用多核的優勢,線上程收集過程中可能會出現頻繁上下文切換,導緻額外的開銷。
           

3.3 Parallel Scavenge

采用複制算法的收集器,和ParNew一樣支援多線程。

但是該收集器重點關心的是吞吐量【吞吐量 = 代碼運作時間 / (代碼運作時間 + 垃圾收集時間) 如果代碼運作100min垃圾收集1min,則為99%】

對于使用者界面,适合使用GC停頓時間短,不然因為卡頓導緻互動界面卡頓将很影響使用者體驗。

對于背景

高吞吐量可以高效率的利用cpu盡快完成程式運算任務,适合背景運算

Parallel Scavenge注重吞吐量,是以也成為"吞吐量優先"收集器。

老年代收集器

3.4 Serial Old

和新生代的Serial一樣為單線程,Serial的老年代版本,不過它采用"标記-整理算法",這個模式主要是給Client模式下的JVM使用。

如果是Server模式有兩大用途

1.jdk5前和Parallel Scavenge搭配使用,jdk5前也隻有這個老年代收集器可以和它搭配。

2.作為CMS收集器的後備。

3.5 Parallel Old

支援多線程,Parallel Scavenge的老年版本,jdk6開始出現, 采用"标記-整理算法"【老年代的收集器大都采用此算法】

在jdk6以前,新生代的Parallel Scavenge隻能和Serial Old配合使用【根據圖,沒有這個的話隻剩Serial Old,而Parallel Scavenge又不能和CMS配合使用】,而且Serial Old為單線程Server模式下會拖後腿【多核cpu下無法充分利用】,這種結合并不能讓應用的吞吐量最大化。

Parallel Old的出現結合Parallel Scavenge,真正的形成“吞吐量優先”的收集器組合。

3.6 CMS

CMS收集器(Concurrent Mark Sweep)是以一種擷取最短回收停頓時間為目标的收集器。【重視響應,可以帶來好的使用者體驗,被sun稱為并發低停頓收集器】

啟用CMS:-XX:+UseConcMarkSweepGC
           

正如其名,CMS采用的是"标記-清除"(Mark Sweep)算法,而且是支援并發(Concurrent)的

它的運作分為4個階段

1.初始标記:标記一下GC Roots能直接關聯到的對象,速度很快
2.并發标記:GC Roots Tarcing過程,即可達性分析
3.重新标記:為了修正因并發标記期間使用者程式運作而産生變動的那一部分對象的标記記錄,會有些許停頓,時間上一般 初始标記 < 重新标記 < 并發标記
4.并發清除
           

以上初始标記和重新标記需要stw(停掉其它運作java線程)

之是以說CMS的使用者體驗好,是因為CMS收集器的記憶體回收工作是可以和使用者線程一起并發執行。

總體上CMS是款優秀的收集器,但是它也有些缺點。

1.cms堆cpu特别敏感,cms運作線程和應用程式并發執行需要多核cpu,如果cpu核數多的話可以發揮它并發執行的優勢,但是cms預設配置啟動的時候垃圾線程數為 (cpu數量+3)/4,它的性能很容易受cpu核數影響,當cpu的數目少的時候比如說為為2核,如果這個時候cpu運算壓力比較大,還要分一半給cms運作,這可能會很大程度的影響到計算機性能。

2.cms無法處理浮動垃圾,可能導緻Concurrent Mode Failure(并發模式故障)而觸發full GC

3.由于cms是采用"标記-清除“算法,是以就會存在垃圾碎片的問題,為了解決這個問題cms提供了 -XX:+UseCMSCompactAtFullCollection選項,這個選項相當于一個開關【預設開啟】,用于CMS頂不住要進行full GC時開啟記憶體碎片合并,記憶體整理的過程是無法并發的,且開啟這個選項會影響性能(比如停頓時間變長)

浮動垃圾:由于cms支援運作的時候使用者線程也在運作,程式運作的時候會産生新的垃圾,這裡産生的垃圾就是浮動垃圾,cms無法當次處理,得等下次才可以。
           

3.7 G1收集器

G1(garbage first:盡可能多收垃圾,避免full gc)收集器是目前最為前沿的收集器之一(1.7以後才開始有),同cms一樣也是關注降低延遲,是用于替代cms功能更為強大的新型收集器,因為它解決了cms産生空間碎片等一系列缺陷。

摘自甲骨文:适用于 Java HotSpot VM 的低暫停、伺服器風格的分代式垃圾回收器。G1 GC 使用并發和并行階段實作其目标暫停時間,并保持良好的吞吐量。當 G1 GC 确定有必要進行垃圾回收時,它會先收集存活資料最少的區域(垃圾優先)

g1的特别之處在于它強化了分區,弱化了分代的概念,是區域化、增量式的收集器,它不屬于新生代也不屬于老年代收集器。

用到的算法為标記-清理、複制算法

jdk1.7,1.8的都是預設關閉的,更高版本的還不知道
開啟選項 -XX:+UseG1GC 
比如在tomcat的catania.sh啟動參數加上
           

g1是區域化的,它将java堆記憶體劃分為若幹個大小相同的區域【region】,jvm可以設定每個region的大小(1-32m,大小得看堆記憶體大小,必須是2的幂),它會根據目前的堆記憶體配置設定合理的region大小。

jdk7中計算region的源碼,這邊部落客看了下也看不怎麼懂,也翻了下openjdk8的看了下關于region的處理似乎不太一樣。。

g1通過并發(并行)标記階段查找老年代存活對象,通過并行複制壓縮存活對象【這樣可以省出連續空間供大對象使用】。

g1将一組或多組區域中存活對象以增量并行的方式複制到不同區域進行壓縮,進而減少堆碎片,目标是盡可能多回收堆空間【垃圾優先】,且盡可能不超出暫停目标以達到低延遲的目的。

g1提供三種垃圾回收模式 young gc、mixed gc 和 full gc,不像其它的收集器,根據區域而不是分代,新生代老年代的對象它都能回收。

幾個重要的預設值,更多的檢視官方文檔oracle官方g1中文文檔

g1是自适應的回收器,提供了若幹個預設值,無需修改就可高效運作
-XX:G1HeapRegionSize=n  設定g1 region大小,不設定的話自己會根據堆大小算,目标是根據最小堆記憶體劃分2048個區域
-XX:MaxGCPauseMillis=200 最大停頓時間 預設200毫秒
           

4 Minor GC、Major GC、FULL GC、mixed gc

4.1 Minor GC

在年輕代

Young space

(包括Eden區和Survivor區)中的垃圾回收稱之為 Minor GC,Minor GC隻會清理年輕代.

4.2 Major GC

Major GC清理老年代(old GC),但是通常也可以指和Full GC是等價,因為收集老年代的時候往往也會伴随着更新年輕代,收集整個Java堆。是以有人問的時候需問清楚它指的是full GC還是old GC。

4.3 Full GC

full gc是對新生代、老年代、永久代【jdk1.8後沒有這個概念了】統一的回收。

【知乎R大的回答:收集整個堆,包括young gen、old gen、perm gen(如果存在的話)、元空間(1.8及以上)等所有部分的模式】

4.4 mixed GC【g1特有】

混合GC

收集整個young gen以及部分old gen的GC。隻有G1有這個模式

5 檢視GC日志

5.1 簡單日志檢視

要看得懂并了解GC,需要看懂GC日志。

這邊我在idea上試了個小例子,需要在idea配置參數(-XX:+PrintGCDetails)。

深入了解JVM-GC
public class GCtest {
    public static void main(String[] args) {
        for(int i = 0; i < 10000; i++) {
            List<String> list = new ArrayList<>();
            list.add("aaaaaaaaaaaaa");
        }
        System.gc();
    }
}
           
[GC (System.gc()) [PSYoungGen: 3998K->688K(38400K)] 3998K->696K(125952K), 0.0016551 secs[
次回收時間]] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 688K->0K(38400K)] [ParOldGen: 8K->603K(87552K)] 696K->603K(125952K), [Metaspace: 3210K->3210K(1056768K)], 0.0121034 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen[年輕代]      total 38400K, used 333K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 1% used [0x0000000795580000,0x00000007955d34a8,0x0000000797600000)
  from space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
  to   space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
 ParOldGen[老年代]       total 87552K, used 603K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740096fe8,0x0000000745580000)
 Metaspace[元空間]      used 3217K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 352K, capacity 388K, committed 512K, reserved 1048576K
           

5.2 離線工具檢視

比如sun的gchisto,gcviewer離線分析工具,做個筆記先了解下還沒用過,可視化好像很好用的樣子。

5.3 自帶的jconsole工具、jstat指令

終端輸入jconsole就會出現jdk自帶的gui監控工具

深入了解JVM-GC

jconsole

可以根據記憶體使用情況間接了解記憶體使用和gc情況

深入了解JVM-GC

jconsole

jstat指令

比如jstat -gcutil pid檢視對應java程序gc情況

深入了解JVM-GC

jstat

s0: 新生代survivor space0簡稱 就是準備複制的那塊 機關為%
s1:指新生代s1已使用百分比,為0的話說明沒有存活對象到這邊
e:新生代eden(伊甸園)區域(%)
o:老年代(%)
ygc:新生代  次數
ygct:minor gc耗時
fgct:full gc耗時(秒)
GCT: ygct+fgct 耗時
           

幾個疑問

1.GC是怎麼判斷對象是被标記的

通過枚舉根節點的方式,通過jvm提供的一種oopMap的資料結構,簡單來說就是不要再通過去周遊記憶體裡的東西,而是通過OOPMap的資料結構去記錄該記錄的資訊,比如說它可以不用去周遊整個棧,而是掃描棧上面引用的資訊并記錄下來。

總結:通過OOPMap把棧上代表引用的位置全部記錄下來,避免全棧掃描,加快枚舉根節點的速度,除此之外還有一個極為重要的作用,可以幫HotSpot實作準确式GC【這邊的準确關鍵就是類型,可以根據給定位置的某塊資料知道它的準确類型,HotSpot是通過oopMap外部記錄下這些資訊,存成映射表一樣的東西】。

2.什麼時候觸發GC

簡單來說,觸發的條件就是GC算法區域滿了或将滿了。

minor GC(young GC):當年輕代中eden區配置設定滿的時候觸發[值得一提的是因為young GC後部分存活的對象
會已到老年代(比如對象熬過15輪),是以過後old gen的占用量通常會變高]

full GC:
①手動調用System.gc()方法 [增加了full GC頻率,不建議使用而是讓jvm自己管理記憶體,
可以設定-XX:+ DisableExplicitGC來禁止RMI調用System.gc]
②發現perm gen(如果存在永久代的話)需配置設定空間但已經沒有足夠空間
③老年代空間不足,比如說新生代的大對象大數組晉升到老年代就可能導緻老年代空間不足。
④CMS GC時出現Promotion Faield[pf]
⑤統計得到的Minor GC晉升到舊生代的平均大小大于老年代的剩餘空間。
這個比較難了解,這是HotSpot為了避免由于新生代晉升到老年代導緻老年代空間不足而觸發的FUll GC。
比如程式第一次觸發Minor GC後,有5m的對象晉升到老年代,姑且現在平均算5m,
那麼下次Minor GC發生時,先判斷現在老年代剩餘空間大小是否超過5m,
如果小于5m,則HotSpot則會觸發full GC(這點挺智能的)
           
Promotion Faield:minor GC時 survivor space放不下[滿了或對象太大],對象隻能放到老年代,而老年代也放不下會導緻這個錯誤。
Concurrent Model Failure:cms時特有的錯誤,因為cms時垃圾清理和使用者線程可以是并發執行的,如果在清理的過程中
可能原因:
1 cms觸發太晚,可以把XX:CMSInitiatingOccupancyFraction調小[比如-XX:CMSInitiatingOccupancyFraction=70 
是指設定CMS在對記憶體占用率達到70%的時候開始GC(因為CMS會有浮動垃圾,是以一般都較早啟動GC)]
2 垃圾産生速度大于清理速度,可能是晉升門檻值設定過小,Survivor空間小導緻跑到老年代,eden區太小,存在大對象、數組對象等情況
3.空間碎片過多,可以開啟空間碎片整理并合理設定周期時間
           
full gc導緻了concurrent mode failure,而不是因為concurrent mode failure錯誤導緻觸發full gc,真正觸發full gc的原因可能是ygc時發生的promotion failure。

3.cms收集器是否會掃描年輕代

會,在初始标記的時候會掃描新生代。

雖然cms是老年代收集器,但是我們知道年輕代的對象是可以晉升為老年代的,為了空間配置設定擔保,還是有必要去掃描年輕代。

4.什麼是空間配置設定擔保

在minor gc前,jvm會先檢查老年代最大可用空間是否大于新生代所有對象總空間,如果是的話,則minor gc可以確定是安全的,

如果擔保失敗,會檢查一個配置(HandlePromotionFailire),即是否允許擔保失敗。

如果允許:繼續檢查老年代最大可用可用的連續空間是否大于之前晉升的平均大小,比如說剩10m,之前每次都有9m左右的新生代到老年代,那麼将嘗試一次minor gc(大于的情況),這會比較冒險。

如果不允許,而且還小于的情況,則會觸發full gc。【為了避免經常full GC 該參數建議打開】

這邊為什麼說是冒險是因為minor gc過後如果出現大對象,由于新生代采用複制算法,survivor無法容納将跑到老年代,是以才會去計算之前的平均值作為一種擔保的條件與老年代剩餘空間比較,這就是配置設定擔保。

這種擔保是動态機率的手段,但是也有可能出現之前平均都比較低,突然有一次minor gc對象變得很多遠高于以往的平均值,這個時候就會導緻擔保失敗【Handle Promotion Failure】,這就隻好再失敗後再觸發一次FULL GC,

5.為什麼複制算法要分兩個Survivor,而不直接移到老年代

這樣做的話效率可能會更高,但是old區一般都是熬過多次可達性分析算法過後的存活的對象,要求比較苛刻且空間有限,而不能直接移過去,這将導緻一系列問題(比如老年代容易被撐爆)

分兩個Survivor(from/to),自然是為了保證複制算法運作以提高效率。

6.各個版本的JVM使用的垃圾收集器是怎麼樣的

準确來說,垃圾收集器的使用跟目前jvm也有很大的關系,比如說g1是jdk7以後的版本才開始出現。

并不是所有的垃圾收集器都是預設開啟的,有些得通過設定相應的開關參數才會使用。比如說cms,需設定(XX:+UseConcMarkSweepGC)

這邊有幾個實用的指令,比如說server模式下

#UnlockExperimentalVMOptions UnlockDiagnosticVMOptions解鎖擷取jvm參數,PrintFlagsFinal用于輸出xx相關參數,以Benchmark類測試,這邊會有很多結果 大都看不懂- - 在這邊查(usexxxxxxgc會看到jvm不同收集器的開關情況)
java -server -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal Benchmark

#後面跟| grep ":"擷取已指派的參數[加:代表被指派過]
java -server -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal Benchmark| grep ":"

#獲得使用者自定義的設定或者jvm設定的詳細的xx參數和值
java -server -XX:+PrintCommandLineFlags Benchmark
           
深入了解JVM-GC

本人用的jdk8,這邊UseParallelGC為true,參考深入了解jvm那本書說這個是Parallel Scavenge+Serial old搭配組合的開關,但是網上又說8預設是Parallel Scavenge+Parallel Old,我還是信書的吧 - -。

更多相關參數

深入了解JVM-GC

常用參數

據說更高版本的jvm預設使用g1

7 stop the world具體是什麼,有沒有辦法避免

stop the world簡單來說就是gc的時候,停掉除gc外的java線程。

無論什麼gc都難以避免停頓,即使是g1也會在初始标記階段發生,stw并不可怕,可以盡可能的減少停頓時間。

8 新生代什麼樣的情況會晉升為老年代

對象優先配置設定在eden區,eden區滿時會觸發一次minor GC

對象晉升規則

1 長期存活的對象進入老年代,對象每熬過一次GC年齡+1(預設年齡門檻值15,可配置)。

2 對象太大新生代無法容納則會配置設定到老年代

3 eden區滿了,進行minor gc後,eden和一個survivor區仍然存活的對象無法放到(to survivor區)則會通過配置設定擔保機制放到老年代,這種情況一般是minor gc後新生代存活的對象太多。

4 動态年齡判定,為了使記憶體配置設定更靈活,jvm不一定要求對象年齡達到MaxTenuringThreshold(15)才晉升為老年代,若survior區相同年齡對象總大小大于survior區空間的一半,則大于等于這個年齡的對象将會在minor gc時移到老年代

8.怎麼了解g1,适用于什麼場景

G1 GC 是區域化、并行-并發、增量式垃圾回收器,相比其他 HotSpot 垃圾回收器,可提供更多可預測的暫停。增量的特性使 G1 GC 适用于更大的堆,在最壞的情況下仍能提供不錯的響應。G1 GC 的自适應特性使 JVM 指令行隻需要軟實時暫停時間目标的最大值以及 Java 堆大小的最大值和最小值,即可開始工作。

g1不再區分老年代、年輕代這樣的記憶體空間,這是較以往收集器很大的差異,所有的記憶體空間就是一塊劃分為不同子區域,每個區域大小為1m-32m,最多支援的記憶體為64g左右,且由于它為了的特性适用于大記憶體機器。

深入了解JVM-GC

g1回收時堆記憶體情況

适用場景:

1.像cms能與應用程式并發執行,GC停頓短【短而且可控】,使用者體驗好的場景。

2.面向服務端,大記憶體,高cpu的應用機器。【網上說差不多是6g或更大】

3.應用在運作過程中經常會産生大量記憶體碎片,需要壓縮空間【比cms好的地方之一,g1具備壓縮功能】。

作者:Garwer

連結:https://www.jianshu.com/p/76959115d486

來源:簡書

著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。