前言 本次講述jvm分代模型的基礎概念,這個專欄會由淺入深的不斷構建起來,按部就班,是很是基礎的內容java
概述: JVM的基礎分代模型:年輕代,老年代,永久代。這裏須要注意內存模型一般是和垃圾回收期相輔相成的,現代的垃圾收集器已經十分複雜了,甚至已經沒有了分代的概念,咱們所講的新生代老年代是最初設計的一些理念,不少人學到更爲先進的垃圾收集器可能會矇蔽,好比堆內存怎麼變成一塊一塊的了,之前不是說堆內存只有新生代和老年代這兩塊完整大的空間呢?spring
年輕代:大部分的對象都是朝生夕滅的。同時JVM老是把對象優先分配在新生代,而且新生代觸發的垃圾回收一般被稱爲 Minor GC。數組
老年代:屬於長期存活的對象貯存地區,由新生代晉升而來。老年代在一般狀況下佔有堆中最大的一塊內存空間。老年代觸發的垃圾回收一般被稱爲 Full GC。app
永久代:注意永久代不等同於方法區,主要存放一些靜態常量或者存放.class類信息,方法區是能夠被垃圾回收的,可是觸發的條件十分苛刻,同時裏面最經常使用的常量池已經在JDK8以後挪到了堆中,將永久代而且更名爲元空間jvm
爲何要把永久代拉進來說呢?其實永久代的設計是一個失敗的設計,JDK8廢棄永久代同時用使用本地內存的元空間來替代,大大減小了永久代溢出的可能,由於已經不使用虛擬機的內存了,而是直接使用本機的內存存儲,還有一個好處是永久代也不會再去搶堆內存了。ide
下面基礎瞭解一下對象分配的基礎概念,這些概念可能在學習JAVA的時候就已經接觸過了,因此也都是簡單提一下:性能
大部分正常對象優先在新生代分配。在垃圾回收線程開啓以後,會將長期存活對象晉升到老年代存儲學習
即便是靜態變量存在於方法區當中的對象,實例對象也是在堆中分配。可是隻有被棧幀局部變量使用的時候纔會觸發初始化。優化
在堆上分配的依然優先選擇在新生代分配,當長期存活以後會晉升老年代長久貯存內存。spa
當新生代的內存空間佔滿的時候,會觸發minor gc。老年代空間被佔滿以後會觸發Full GC,同時Full GC
下面用一張圖解釋對象分配的基礎概念:
咱們如下的代碼爲例簡單講解對象分配的方式。
public class OneWeek { private static final Properties properties = new Properties(); public static void main(String[] args) throws IOException { InputStream resourceAsStream = OneWeek.class.getClassLoader().getResourceAsStream("app.properties"); properties.load(resourceAsStream); System.out.println("load properties user.name = " + properties.getProperty("user.name")); }/*運行結果: load properties user.name = 123 #app.properties: user.name=123 */ }
首先,當線程開啓的時候,首先會加載而且初始化OneWeek.class對象,同時將Main()方法壓入到虛擬機棧中,同時建立棧幀以及局部變量表等內容。
而後,執行字節碼引擎執行字節碼裏面的指令,根據代碼能夠看到,方法首先會拿到當前類的class文件,而且調用當前類加載器加載app.properties
這個文件到內存當中,注意在加載的過程當中會建立char[]數組存儲加載的內容,以及建立文件IO流讀取文件等操做,這部分的對象都是優先分配在新生代的。
當程序計數器執行到:properties.load(resourceAsStream);
這一行代碼對應的字節碼指令的時候,會發現Properties.class沒有加載,同時又發現他是一個靜態對象,因此會把當前的對象引用分配到方法區進行貯存,注意方法區存放的是對象的引用不是對象的實例,實例依舊優先分配在新生代。可是這裏爲何直接劃分到老年代了呢?由於咱們知道這個靜態常量若是被其餘的類引用,那麼能夠算做是長期存活對象,那麼長期存活的對象早晚是要進入到老年代的,因此圖中直接劃分到老年代了。
同時咱們也能夠發現若是新生代在垃圾回收以後存在長期存活的對象,會在垃圾回收以後自動晉升到老年代進行存儲。
特別要注意咱們平時new出來的對象都是強引用。哪怕是棧幀局部變量只被使用過一次對象的引用隨着棧幀回收,也是不會立馬回收的,而是要等到垃圾回收線程開啓以後被回收掉。
下面分別說明一下這四個點是如何來的:
什麼是對象年齡?對象年齡就是在JVM運行的時候,新生代中的對象只要每躲過一次垃圾回收,內部的引用計數器就會把當前年齡的對象+1,當對象的年齡累加到15以後,該對象在下一次垃圾回收以後就會晉升到老年代。固然此時並非高枕無憂了,當老年代也被佔滿的時候若是當前對象已經沒有被GC ROOT引用了,也仍是會被當作垃圾回收的。
大對象最典型的案例就是大字符串或者很大的字節數組,由於須要佔用 連續的內存空間,若是新生代沒法容納,那麼毫無疑問是須要老年代做爲兜底放到老年代直接存放,至於具體參數後續的文章會一一解釋,這裏瞭解基礎概念便可。
老年代何時會觸發垃圾回收的操做?條件毫無疑問是老年代放不下對象了,那麼老年代爲何會滿的,上一段咱們說過老年代的對象都是重新生代來的,因此毫無疑問是新生代來到老年代發現老年代放不下了,因此老年代此時就會進行垃圾回收了,老年代的回收叫作Full GC。
看完上面這些,咱們須要考慮的是 新生代進入老年代的時機,爲何要考慮這個東西,咱們來分析一下:
首先是大對象,大對象進入新生代發現新生代放不下,若是老年代也發現放不下就直接Full GC了?這未免也太悲觀了,萬一垃圾回收以後放下來了,那不是白白浪費性能,不合適。其次,新生代必定要等到本身滿了才進入老年代麼,這樣未免又太樂觀了,由於萬一新生代總有一些存活對象活在「等待區」(survior區)又不願進入老年代,中間賴着不走,那麼這一片區域反而失去了他的價值,因此也是不合適的,不如提早進入老年代。
分代的核心參數以下,須要注意的是要注意區分大小寫,輸錯會致使參數不生效:
-Xms
:java堆內存的大小
-Xmx
:java堆內存的最大大小
-Xmn
:java堆當中的新生代大小,扣除新生代剩下就是老年代的內存大小
-XX:PermSize
:永久代大小**(JDK8廢棄,被替換爲:-XX:MetaspaceSize)**
-XX:MaxPermSize
:永久代最大大小**(JDK8廢棄,被替換爲:-XX:MaxMetaspceSize)**
-Xss
:每一個線程棧內存大小
-xms和-xmx用來限定java堆的總大小以及擴張的最大大小,可是一般會設置爲同樣的參數,由於擴容須要
stop world
極大的影響系統性能。-Xmn:是新生代的空間大小,老年代會自動根據總的堆大小 - 新生代大小算出來。
-xx:permsize和-xx:maxpermsize。會限制永久代大小和最大的大小,一般狀況下設置爲256M夠用
- Xss 參數限制 每個線程的棧內存大小。其實就是每個線程對應虛擬機棧的大小,注意這區域不能太大,固然也不能過小。
-XX:MetaspaceSize
以及 -XX:MaxMetaspceSize
。 在IDEA當中的啓動參數設置以下:
-Xms1024m -Xmx2048m -XX:ReservedCodeCacheSize=500m -XX:+UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB=50 -ea -XX:CICompilerCount=2 -Dsun.io.useCanonPrefixCache=false -Djava.net.preferIPv4Stack=true -Djdk.http.auth.tunneling.disabledSchemes="" -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Djdk.attach.allowAttachSelf=true -Dkotlinx.coroutines.debug=off -Djdk.module.illegalAccess.silent=true
Tomcat在bin的Catalina 下面進行參數配置。
Spring boot 直接在vm.options裏面加入虛擬機參數便可
首先該類的全部實例對象都已經被回收
加載該類的classLoader類加載器已經被回收
對該class對象已經沒有任何引用。
知足上面這些條件就能夠被回收,能夠發現方法區的回收條件十分的嚴格。
本文講述了JVM的分代模型,新生代,老年代,接着,咱們對於JVM對象在分代裏面分配的一些基礎概念,好比對象優先分配在老年代,對象年齡晉升到老年代以及垃圾回收以後長期存活對象進入老年代,一樣,JVM也存在一些特殊的判斷機制讓新生代提早進入老年代,這些都是十分重要的優化。
瞭解分代的概念以及熟悉JVM的內存模型是很是重要的,由於現代垃圾收集器不斷進化以及複雜甚至放棄分代的理念,十分有必要了解分代的歷史以及分代的進程,同時不分代勢必會是將來趨勢。