從這篇開始我們探討一些jvm調優的問題。在jvm調優中一個離不開的重點是垃圾回收,當垃圾回收成為系統達到更高併發量的瓶頸時,我們就需要對jvm中如果進行“自動化”垃圾回收技術實施必要的監控和調節。
對於調優之前,我們必須要了解其運行原理,java 的垃圾收集Garbage Collection 通常被稱為“GC”,它誕生於1960年 MIT 的 Lisp 語言,經過半個多世紀,目前已經十分成熟了。因此本篇主要從這兩個方面來了解:
1. 哪些對象需要被回收?
2. 如何回收?
一、誰要被回收
java虛擬機在執行java程序的過程中會把它所管理的內存劃分為若干個不同是數據區域,這些區域有各自各自的用途。主要包含以下幾個部分組成:
1、程序計數器
程序計數器佔用的內存空間我們可以忽略不計,它是每個線程所執行的字節碼的行號指示器。
2、虛擬機棧
java的虛擬機棧是線程私有的,生命週期和線程相同。它描述的是方法執行的內存模型。同時用於存儲局部變量、操作數棧、動態鏈接、方法出口等。
3、本地方法棧
本地方法棧,類似虛擬機棧,它調用的是是native方法。
4、堆
堆是jvm中管理內存中最大一塊。它是被共享,存放對象實例。也被稱為“gc堆”。垃圾回收的主要管理區域
5、方法區
方法區也是共享的內存區域。它主要存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器(jit)編譯後的代碼數據。
以上就是jvm在運行時期主要的內存組成,我們看到常見的內存使用不但存在於堆中,還會存在於其他區域,雖然堆的管理對程序的管理至關重要,但我們不能只侷限於這一個區域,特別是當出現內存洩露的時候,我們除了要排查堆內存的情況,還得考慮虛擬機棧的以及方法區域的情況。
知道了要對誰以及那些區域進行內存管理,我還需要知道什麼時候對這些區域進行垃圾回收。
二、如何回收
上述的兩點講解之後,我們大概明白了,哪些對象會被回收,以及回收的依據是什麼,但回收的這個工作實現起來並不簡單,首先它需要掃描所有的對象,鑑別誰能夠被回收,其次在掃描期間需要 ”stop the world“ 對象能被凍結,不然你剛掃描,他的引用信息有變化,你就等於白做了。
分代回收
我們從一個object1來說明其在分代垃圾回收算法中的回收軌跡。
1、object1新建,出生於新生代的Eden區域。
2、minor GC,object1 還存活,移動到Fromsuvivor空間,此時還在新生代。
3、minor GC,object1 仍然存活,此時會通過複製算法,將object1移動到ToSuv區域,此時object1的年齡age+1。
4、minor GC,object1 仍然存活,此時survivor中和object1同齡的對象並沒有達到survivor的一半,所以此時通過複製算法,將fromSuv和Tosuv 區域進行互換,存活的對象被移動到了Tosuv。
5、minor GC,object1 仍然存活,此時survivor中和object1同齡的對象已經達到survivor的一半以上(toSuv的區域已經滿了),object1被移動到了老年代區域。
6、object1存活一段時間後,發現此時object1不可達GcRoots,而且此時老年代空間比率已經超過了閾值,觸發了majorGC(也可以認為是fullGC,但具體需要垃圾收集器來聯繫),此時object1被回收了。fullGC會觸發 stop the world。
在以上的新生代中,我們有提到對象的age,對象存活於survivor狀態下,不會立即晉升為老生代對象,以避免給老生代造成過大的影響,它們必須要滿足以下條件才可以晉升:
1、minor gc 之後,存活於survivor 區域的對象的age會+1,當超過(默認)15的時候,轉移到老年代。
2、動態對象,如果survivor空間中相同年齡所有的對象大小的綜合和大於survivor空間的一半,年級大於或等於該年級的對象就可以直接進入老年代。
以上採用分代垃圾收集的思想,對一個對象從存活到死亡所經歷的歷程。期間,在新生代的時刻,會用到複製算法,在老年代時,有可能會用到標記-清楚算法(mark-sweep)算法或者標記-整理算法,這些都是垃圾回收算法基於不同區域的實現,我們看下這幾種回收算法的實現原理。
閱讀更多 Java高併發框架 的文章