詳解JVM的分代模型
前言
上篇文章我們一起對jvm的內(nèi)存模型有了比較清晰的認(rèn)識,小伙伴們可以參考JVM內(nèi)存模型不再是秘密這篇文章做一個復(fù)習(xí)。
本篇文章我們將針對jvm堆內(nèi)存的分代模型做一個詳細(xì)的解析,和大家一起輕松理解jvm的分代模型。
相信看過其他文章的小伙伴們可能都知道,jvm的分代模型包括:年輕代、老年代、永久代。
那么它們分別代表著什么角色呢?我們先來看一段代碼
public class Main { public static void main(String[] args) { while (true){ load(); } } public static void load(){ SysUser sysUser = new SysUser(); sysUser.setAvatar("1"); } }
這段代碼本身沒有什么特殊的含義,主要是理解jvm的運(yùn)行機(jī)制。
首先一旦執(zhí)行main()方法,就會把main()方法的棧幀壓入main線程的虛擬機(jī)棧,然后調(diào)用load()方法后,又會把load()方法的棧幀壓入虛擬機(jī)棧。
接著在執(zhí)行l(wèi)oad()方法時,會在java堆內(nèi)存中創(chuàng)建一個SysUser對象實(shí)例,而棧幀中會有sysUser局部變量引用堆內(nèi)存中的SysUser對象實(shí)例。
如下圖:
到這里上篇文章都講解過,相信大家都能看懂。
變量的存活時間
現(xiàn)在我們思考一下會發(fā)現(xiàn),這個SysUser對象實(shí)際上屬于一個短暫存活的對象,因?yàn)樵趌oad()方法執(zhí)行完畢后,load()方法的棧幀就會出棧。
而一旦出棧,就沒有了sysUser這個局部變量來引用SysUser這個對象的實(shí)例。
所以,其實(shí)這個SysUser對象已經(jīng)沒有用了,但是它還在占用著堆內(nèi)存的空間,那么對于這種沒有引用的對象實(shí)例jvm是如何處理的呢?
這就要說到j(luò)vm的垃圾回收機(jī)制了,jvm本身是有垃圾回收機(jī)制的,它是一個后臺線程,會把沒有人引用的SysUser對象實(shí)例給回收掉,不斷的釋放內(nèi)存空間。
所以這個SysUser對象實(shí)例是一個存活時間很短的對象,可能在執(zhí)行l(wèi)oad()方法的時候被創(chuàng)建出來,執(zhí)行之后就被垃圾回收掉了。
而這種對象在我們平時的開發(fā)中是很常見的,占絕大多數(shù)比例。
現(xiàn)在我們將上邊的代碼改造一下:
public class Main { private static SysUser sysUser = new SysUser(); public static void main(String[] args) { while (true){ load(); } } public static void load(){ sysUser.setAvatar("1"); } }
其實(shí)就是把局部變量sysUser變成了靜態(tài)變量,這樣修改后,sysUser不在作為局部變量保存在棧中,而是和class類文件一起保存在方法區(qū)中,這樣SysUser對象實(shí)例就會一直被這個靜態(tài)變量引用,所以不會被垃圾回收,一直保存在堆內(nèi)存中。如下圖:
分代模型
接下來我們進(jìn)入核心內(nèi)容,就是jvm的分代模型了。
上文中我們發(fā)現(xiàn),根據(jù)我們的編碼方式的不同,采用不同的方式創(chuàng)建和使用對象,對象的存活時間是不同的。
所以jvm將內(nèi)存區(qū)分為兩個區(qū)域:年輕代和老年代。
年輕代就是我們的第一種局部變量的示例,創(chuàng)建和使用完畢后會被垃圾回收掉。
老年代就是第二種靜態(tài)變量的示例,創(chuàng)建后需要長期在堆內(nèi)存中存活。
相信到這里大家就應(yīng)該理解了什么樣的對象是短期存活的對象,什么樣的對象是長期存活的對象,那么它們是如何分別存在年輕代和老年代中的呢?為什么要這么區(qū)分呢?
其實(shí)這與垃圾回收機(jī)制是密不可分的。
對于年輕代里的對象,他們的特點(diǎn)是創(chuàng)建后很快就會被回收,而對于老年代里的對象,他們的特點(diǎn)是需要長期存活,所以這兩種對象是不能用一種垃圾回收算法進(jìn)行回收的,所以需要區(qū)分成兩個。
對于長期存在的靜態(tài)變量sysUser,其實(shí)剛開始的時候也是在年輕代的,那它是什么時候進(jìn)入老年代的呢?我們下文會講解這個問題。
那永久代又是什么呢?其實(shí)永久代就是我們說的jvm的方法區(qū),用于存放一下類信息的,這部分之后的文章涉及到會詳解,現(xiàn)在理解到這就可以了。
新生代的垃圾回收
前文我們了解了,當(dāng)load方法執(zhí)行完畢出棧后,里面的局部變量sysUser就沒了,堆內(nèi)存中的SysUser對象就沒有引用了,所以會被垃圾回收掉。
那么問題來了,是沒有引用后就會立即發(fā)生垃圾回收,回收掉沒有被引用的對象實(shí)例嗎?
其實(shí)不是這樣的,垃圾回收是有觸發(fā)條件的。
有一個比較常見的場景是這樣的,假設(shè)我們的代碼中創(chuàng)建了大量的對象,導(dǎo)致堆內(nèi)存中囤積了大量的對象,然后這些對象現(xiàn)在都沒有人引用了。
這個時候,如果新生代預(yù)先分配的內(nèi)存空間被占滿了,那么我們的代碼此時要新創(chuàng)建一個對象的時候,發(fā)現(xiàn)新生代空間滿了,怎么辦?
這個時候就會觸發(fā)一次新生代的垃圾回收,也稱為“Minor GC”或"Young GC",它會嘗試把新生代中沒有人引用的對象給回收掉,釋放空間。
下圖表達(dá)了這一過程:
長期存活的對象什么時候進(jìn)入老年代
接下來我們談?wù)撘粋€話題,靜態(tài)變量引用的長期存活的對象是什么時候進(jìn)入老年代的。
上文我們了解到,新生代的對象會經(jīng)歷一次次的垃圾回收,而被靜態(tài)變量引用的對象因?yàn)橐恢北灰茫砸恢辈粫换厥?,所以此時jvm就有了一條規(guī)定。
如果新生代中的對象,在經(jīng)歷了15次垃圾回收后,依然堅(jiān)挺的存活著,那就證明它是個"老年人"了,然后它會被轉(zhuǎn)移到老年代中。
老年代就是存放這些年齡比較大的對象的。
那么老年代中的對象會被垃圾回收嗎?
答案是肯定的,因?yàn)槔夏甏锏膶ο箅S著代碼的運(yùn)行,也是可以不再被任何人引用的,就需要垃圾回收了。
或者說,隨著越來越多的對象進(jìn)入老年代,老年代的內(nèi)存也會被占滿,所以一定是要對老年代進(jìn)行垃圾回收的。
我們暫時不用考慮具體是怎么回收的,這個內(nèi)容在之后的文章中我們會有詳細(xì)的解析。
總結(jié)
今天就給大家準(zhǔn)備了這么多內(nèi)容,可能有些小伙伴覺得還沒看夠,這些內(nèi)容都比較簡單,我已經(jīng)會了,有沒有更深入的東西呢?
別急,學(xué)習(xí)是循序漸進(jìn)的事情,王子是想要用最簡單的大白話來和小伙伴們一起討論jvm的原理的,同時也想找一些案例來和大家一起探討,印象會更深刻。
所以今天小伙伴們了解到這里就可以了,讓我們在后續(xù)的文章中不見不散,深入討論些更深層的內(nèi)容吧。
以上就是詳解JVM的分代模型的詳細(xì)內(nèi)容,更多關(guān)于JVM 分代模型的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring實(shí)戰(zhàn)之Qualifier注解用法示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之Qualifier注解用法,結(jié)合實(shí)例形式詳細(xì)分析了spring Qualifier注解相關(guān)配置、定義與使用方法,需要的朋友可以參考下2019-12-12springboot 使用yml配置文件給靜態(tài)變量賦值教程
這篇文章主要介紹了springboot 使用yml配置文件給靜態(tài)變量賦值教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04java正則表達(dá)式校驗(yàn)日期格式實(shí)例代碼
如果使用得當(dāng),正則表達(dá)式是匹配各種模式的強(qiáng)大工具,下面這篇文章主要給大家介紹了關(guān)于java正則表達(dá)式校驗(yàn)日期格式的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05深入淺出重構(gòu)Mybatis與Spring集成的SqlSessionFactoryBean(上)
通常來講,重構(gòu)是指不改變功能的情況下優(yōu)化代碼,但本文所說的重構(gòu)也包括了添加功能。這篇文章主要介紹了重構(gòu)Mybatis與Spring集成的SqlSessionFactoryBean(上)的相關(guān)資料,需要的朋友可以參考下2016-11-11