輕鬆理解JVM的分代模型

 

前言

上篇文章我們一起對jvm的內存模型有了比較清晰的認識,小夥伴們可以參考JVM內存模型不再是祕密這篇文章做一個複習。

本篇文章我們將針對jvm堆內存的分代模型做一個詳細的解析,和大家一起輕鬆理解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的運行機制。

首先一旦執行main()方法,就會把main()方法的棧幀壓入main線程的虛擬機棧,然後調用load()方法後,又會把load()方法的棧幀壓入虛擬機棧。

接着在執行load()方法時,會在java堆內存中創建一個SysUser對象實例,而棧幀中會有sysUser局部變量引用堆內存中的SysUser對象實例。

如下圖:

 

 到這裏上篇文章都講解過,相信大家都能看懂。

 

變量的存活時間

現在我們思考一下會發現,這個SysUser對象實際上屬於一個短暫存活的對象,因爲在load()方法執行完畢後,load()方法的棧幀就會出棧。

而一旦出棧,就沒有了sysUser這個局部變量來引用SysUser這個對象的實例。

所以,其實這個SysUser對象已經沒有用了,但是它還在佔用着堆內存的空間,那麼對於這種沒有引用的對象實例jvm是如何處理的呢?

這就要說到jvm的垃圾回收機制了,jvm本身是有垃圾回收機制的,它是一個後臺線程,會把沒有人引用的SysUser對象實例給回收掉,不斷的釋放內存空間。

所以這個SysUser對象實例是一個存活時間很短的對象,可能在執行load()方法的時候被創建出來,執行之後就被垃圾回收掉了。

而這種對象在我們平時的開發中是很常見的,佔絕大多數比例。

 

現在我們將上邊的代碼改造一下:

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");
    }
}

其實就是把局部變量sysUser變成了靜態變量,這樣修改後,sysUser不在作爲局部變量保存在棧中,而是和class類文件一起保存在方法區中,這樣SysUser對象實例就會一直被這個靜態變量引用,所以不會被垃圾回收,一直保存在堆內存中。如下圖:

 

 

分代模型

接下來我們進入核心內容,就是jvm的分代模型了。

上文中我們發現,根據我們的編碼方式的不同,採用不同的方式創建和使用對象,對象的存活時間是不同的。

所以jvm將內存區分爲兩個區域:年輕代和老年代

年輕代就是我們的第一種局部變量的示例,創建和使用完畢後會被垃圾回收掉。

老年代就是第二種靜態變量的示例,創建後需要長期在堆內存中存活。

相信到這裏大家就應該理解了什麼樣的對象是短期存活的對象,什麼樣的對象是長期存活的對象,那麼它們是如何分別存在年輕代和老年代中的呢?爲什麼要這麼區分呢?

其實這與垃圾回收機制是密不可分的。

對於年輕代裏的對象,他們的特點是創建後很快就會被回收,而對於老年代裏的對象,他們的特點是需要長期存活,所以這兩種對象是不能用一種垃圾回收算法進行回收的,所以需要區分成兩個。

對於長期存在的靜態變量sysUser,其實剛開始的時候也是在年輕代的,那它是什麼時候進入老年代的呢?我們下文會講解這個問題。

那永久代又是什麼呢?其實永久代就是我們說的jvm的方法區,用於存放一下類信息的,這部分之後的文章涉及到會詳解,現在理解到這就可以了。

 

新生代的垃圾回收

前文我們瞭解了,當load方法執行完畢出棧後,裏面的局部變量sysUser就沒了,堆內存中的SysUser對象就沒有引用了,所以會被垃圾回收掉。

那麼問題來了,是沒有引用後就會立即發生垃圾回收,回收掉沒有被引用的對象實例嗎?

其實不是這樣的,垃圾回收是有觸發條件的。

有一個比較常見的場景是這樣的,假設我們的代碼中創建了大量的對象,導致堆內存中囤積了大量的對象,然後這些對象現在都沒有人引用了。

這個時候,如果新生代預先分配的內存空間被佔滿了,那麼我們的代碼此時要新創建一個對象的時候,發現新生代空間滿了,怎麼辦?

這個時候就會觸發一次新生代的垃圾回收,也稱爲“Minor GC”或"Young GC",它會嘗試把新生代中沒有人引用的對象給回收掉,釋放空間。

下圖表達了這一過程:

 

 

長期存活的對象什麼時候進入老年代

接下來我們談論一個話題,靜態變量引用的長期存活的對象是什麼時候進入老年代的。

上文我們瞭解到,新生代的對象會經歷一次次的垃圾回收,而被靜態變量引用的對象因爲一直被引用,所以一直不會被回收,所以此時jvm就有了一條規定。

如果新生代中的對象,在經歷了15次垃圾回收後,依然堅挺的存活着,那就證明它是個"老年人"了,然後它會被轉移到老年代中。

老年代就是存放這些年齡比較大的對象的。

那麼老年代中的對象會被垃圾回收嗎?

答案是肯定的,因爲老年代裏的對象隨着代碼的運行,也是可以不再被任何人引用的,就需要垃圾回收了。

或者說,隨着越來越多的對象進入老年代,老年代的內存也會被佔滿,所以一定是要對老年代進行垃圾回收的。

我們暫時不用考慮具體是怎麼回收的,這個內容在之後的文章中我們會有詳細的解析。

 

總結

今天就給大家準備了這麼多內容,可能有些小夥伴覺得還沒看夠,這些內容都比較簡單,我已經會了,有沒有更深入的東西呢?

別急,學習是循序漸進的事情,王子是想要用最簡單的大白話來和小夥伴們一起討論jvm的原理的,同時也想找一些案例來和大家一起探討,印象會更深刻。

所以今天小夥伴們瞭解到這裏就可以了,讓我們在後續的文章中不見不散,深入討論些更深層的內容吧。

 

 

往期文章推薦:

大白話談JVM的類加載機制

JVM內存模型不再是祕密

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章