008、聊聊JVM分代模型:年輕代、老年代、永久代

聊聊 JVM 分代模型:年輕代、老年代、永久代
目錄:
背景引入
大部分對(duì)象其實(shí)存活周期極短
少數(shù)對(duì)象是長(zhǎng)期存活的
JVM分代模型:年輕代和老年代
為什么要分成年輕代和老年代?
什么是永久代?
上周思考題解答
今日思考題
1、背景引入
今天開(kāi)始,咱們進(jìn)入第二周的內(nèi)容,這一周會(huì)重點(diǎn)關(guān)注JVM內(nèi)存劃分的一些細(xì)節(jié)。
我會(huì)幫助大家更加深入的去理解JVM內(nèi)存劃分的原理細(xì)節(jié),以及我們創(chuàng)建的那些對(duì)象在JVM中到底是如何分配,如何流動(dòng)的,這對(duì)于大家理解JVM原理有更深一層的幫助。
首先這篇文章作為本周的一個(gè)開(kāi)篇,我們來(lái)給大家介紹一下JVM內(nèi)存的一個(gè)分代模型:年輕代、老年代、永久代。
大家現(xiàn)在應(yīng)該都知道一點(diǎn),那就是我們?cè)诖a里創(chuàng)建的對(duì)象,都會(huì)進(jìn)入到Java堆內(nèi)存中,比如下面的代碼:

這段代碼,我們稍微做了點(diǎn)改動(dòng),在main()方法里,會(huì)周期新的執(zhí)行loadReplicasFromDisk()方法,加載副本數(shù)據(jù)。
首先一旦執(zhí)行main()方法,那么就會(huì)把main()方法的棧幀壓入main線程的Java虛擬機(jī)棧
如下圖。

然后每次在while循環(huán)里,調(diào)用loadReplicasFromDisk()方法,就會(huì)把loadReplicasFromDisk()方法的棧幀壓入自己的Java虛擬機(jī)棧
如下圖。

接著在執(zhí)行l(wèi)oadReplicasFromDisk()方法的時(shí)候,會(huì)在Java堆內(nèi)存里會(huì)創(chuàng)建一個(gè)ReplicaManager對(duì)象實(shí)例
而且loadReplicasFromDisk()方法的棧幀里會(huì)有“replicaManager”局部變量去引用Java堆內(nèi)存里的ReplicaManager對(duì)象實(shí)例
如下圖:

然后就會(huì)執(zhí)行ReplicaManager對(duì)象的load()方法。
2、大部分對(duì)象都是存活周期極短的
現(xiàn)在有一個(gè)問(wèn)題,在上面代碼中,那個(gè)ReplicaManager對(duì)象,實(shí)際上屬于短暫存活的這么一個(gè)對(duì)象
大家可以觀察一下,在loadReplicasFromDisk()方法中創(chuàng)建這個(gè)對(duì)象,然后執(zhí)行ReplicaManager對(duì)象的load()方法,然后執(zhí)行完畢之后,loadReplicasFromDisk()方法就會(huì)結(jié)束。
一旦方法結(jié)束,那么loadReplicasFromDisk()方法的棧幀就會(huì)出棧,如下圖。

然后接著上篇文章已經(jīng)說(shuō)過(guò),此時(shí)一旦沒(méi)人引用這個(gè)ReplicaManager對(duì)象了,就會(huì)被JVM的垃圾回收線程給回收掉,釋放內(nèi)存空間,如下圖。

然后在main()方法的while循環(huán)里,下一次循環(huán)再次執(zhí)行loadReplicasFromDisk()方法的時(shí)候,又會(huì)走一遍上面那個(gè)過(guò)程,把loadReplicasFromDisk()方法的棧幀壓入Java虛擬機(jī)棧,然后構(gòu)造一個(gè)ReplicaManager實(shí)例對(duì)象放在Java堆里。
一旦執(zhí)行完ReplicaManager對(duì)象的load()方法之后,loadReplicasFromDisk()方法又會(huì)結(jié)束,再次出棧,然后垃圾回收釋放掉Java堆內(nèi)存里的ReplicaManager對(duì)象。
所以其實(shí)這個(gè)ReplicaManager對(duì)象,在上面的代碼中,是一個(gè)存活周期極為短暫的對(duì)象
可能每次執(zhí)行l(wèi)oadReplicasFromDisk()方法的時(shí)候,被創(chuàng)建出來(lái),然后執(zhí)行他的load()方法,接著可能1毫秒之后,就被垃圾回收掉了。
所以從這段代碼就可以明顯看出來(lái),大部分在我們代碼里創(chuàng)建的對(duì)象,其實(shí)都是存活周期很短的。這種對(duì)象,其實(shí)在我們寫(xiě)的Java代碼中,占到絕大部分的比例。
3、少數(shù)對(duì)象是長(zhǎng)期存活的
但是我們來(lái)看另外一段代碼,假如說(shuō)咱們用下面的這種方式來(lái)實(shí)現(xiàn)同樣的功能:

上面那段代碼的意思,就是給Kafka這個(gè)類(lèi)定義一個(gè)靜態(tài)變量,也就是“replicaManager”,這個(gè)Kafka類(lèi)是在JVM的方法區(qū)里的
然后讓“replicaManager”引用了一個(gè)在Java堆內(nèi)存里創(chuàng)建的ReplicaManager實(shí)例對(duì)象,如下圖。

接著在main()方法中,就會(huì)在一個(gè)while循環(huán)里,不停的調(diào)用ReplicaManager對(duì)象的load()方法,做成一個(gè)周期性運(yùn)行的模式。
這個(gè)時(shí)候,我們就要來(lái)思考一下,這個(gè)ReplicaManager實(shí)例對(duì)象,他是會(huì)一直被Kafka的靜態(tài)變量引用的,然后會(huì)一直駐留在Java堆內(nèi)存里,是不會(huì)被垃圾回收掉的。
因?yàn)檫@個(gè)實(shí)例對(duì)象他需要長(zhǎng)期被使用,周期新的被調(diào)用load()方法,所以他就成為了一個(gè)長(zhǎng)時(shí)間存在的對(duì)象。
那么類(lèi)似這種被類(lèi)的靜態(tài)變量長(zhǎng)期引用的對(duì)象,他需要長(zhǎng)期停留在Java堆內(nèi)存里,這這種對(duì)象就是生存周期很長(zhǎng)的對(duì)象,他是輕易不會(huì)被垃圾回收的,他需要長(zhǎng)期存在,不停的去使用他。
4、JVM分代模型:年輕代和老年代
接下來(lái)就要進(jìn)入今天的核心主題了,就是JVM的分代模型,年輕代和老年代。
現(xiàn)在大家已經(jīng)看到,其實(shí)根據(jù)你寫(xiě)代碼方式的不同,采用不同的方式來(lái)創(chuàng)建和使用對(duì)象,其實(shí)對(duì)象的生存周期是不同的。
所以JVM將Java堆內(nèi)存劃分為了兩個(gè)區(qū)域,一個(gè)是年輕代,一個(gè)是老年代。
其中年輕代,顧名思義,就是把第一種代碼示例中的那種,創(chuàng)建和使用完之后立馬就要回收的對(duì)象放在里面
然后老年代呢,就是把第二種代碼示例中的那種,創(chuàng)建之后需要一直長(zhǎng)期存在的對(duì)象放在里面,大家看下圖:

比如下面的代碼,我們?cè)俅蝸?lái)改造一下,再結(jié)合圖,大家會(huì)看的更加的明確一些。

上面那段代碼稍微復(fù)雜了點(diǎn),我們解釋一下
Kafka的靜態(tài)變量“fetcher”引用了ReplicaFetcher對(duì)象,這是長(zhǎng)期需要駐留在內(nèi)存里使用的
這個(gè)對(duì)象會(huì)在年輕代里停留一會(huì)兒,但是最終會(huì)進(jìn)入老年代,大家看下圖。

進(jìn)入main()方法之后,會(huì)先調(diào)用loadReplicasFromDisk()方法,業(yè)務(wù)含義是系統(tǒng)啟動(dòng)就從磁盤(pán)加載一次副本數(shù)據(jù),這個(gè)方法的棧幀會(huì)入棧
然后在這個(gè)方法里面創(chuàng)建了一個(gè)ReplicaManager對(duì)象,這個(gè)對(duì)象他是用完就會(huì)回收,所以是會(huì)放在年輕代里的,由棧幀里的局部變量來(lái)引用
此時(shí)對(duì)應(yīng)著下圖:

然后一旦loadReplicasFromDisk()方法執(zhí)行完畢了,方法的棧幀就會(huì)出棧,對(duì)應(yīng)的年輕代里的ReplicaManager對(duì)象也會(huì)被回收掉,如下圖:

但是接著會(huì)執(zhí)行一段while循環(huán)代碼,他會(huì)周期性的調(diào)用ReplicaFetcher的fetch()方法,去從遠(yuǎn)程加載副本數(shù)據(jù)。
所以ReplicaFetcher這個(gè)對(duì)象因?yàn)楸籏afka類(lèi)的靜態(tài)變量fetcher給引用了,所以他會(huì)長(zhǎng)期存在于老年代里的,持續(xù)被使用。
5、為什么要分成年輕代和老年代?
相信看完這篇文章,大家就一定看明白了,什么樣的對(duì)象是短期存活的對(duì)象,什么樣的對(duì)象是長(zhǎng)期存在的對(duì)象,然后如何分別存在于年輕代和老年代里。
那么為什么需要這么區(qū)分呢?
因?yàn)檫@跟垃圾回收有關(guān),對(duì)于年輕代里的對(duì)象,他們的特點(diǎn)是創(chuàng)建之后很快就會(huì)被回收,所以需要用一種垃圾回收算法
對(duì)于老年代里的對(duì)象,他們的特點(diǎn)是需要長(zhǎng)期存在,所以需要另外一種垃圾回收算法,所以需要分成兩個(gè)區(qū)域來(lái)放不同的對(duì)象。
很多人又會(huì)問(wèn)了,你不是說(shuō)“ReplicaFetcher”這個(gè)長(zhǎng)期存在的對(duì)象,剛開(kāi)始也在年輕代,后來(lái)才會(huì)進(jìn)入老年代么?那他到底什么時(shí)候進(jìn)入老年代?
別急,明天的文章就會(huì)分析這塊。
然后還有人還會(huì)問(wèn)了,那么年輕代和老年代分別怎么進(jìn)行垃圾回收呢?
別急,下周的文章會(huì)主要分析垃圾回收這塊的原理。這周我們主要關(guān)注JVM的內(nèi)存劃分的細(xì)節(jié),搞明白對(duì)象是如何在不同的內(nèi)存區(qū)域里分配的就可以了,學(xué)習(xí)要循序漸進(jìn)。
6、什么是永久代?
很簡(jiǎn)單,JVM里的永久代其實(shí)就是我們之前說(shuō)的方法區(qū)
上面那個(gè)圖里的方法區(qū),其實(shí)就是所謂的永久代,你可以認(rèn)為永久代就是放一些類(lèi)信息的。
這個(gè)話題現(xiàn)在不用過(guò)多考慮,后續(xù)涉及到的時(shí)候,我們會(huì)講到的。
7、上周思考題解答
上周留了一個(gè)思考題,讓大家思考方法區(qū)內(nèi)會(huì)不會(huì)進(jìn)行垃圾回收
其實(shí)有同學(xué)都回答了,非常的好,在以下幾種情況下,方法區(qū)里的類(lèi)會(huì)被回收。
首先該類(lèi)的所有實(shí)例對(duì)象都已經(jīng)從Java堆內(nèi)存里被回收
其次加載這個(gè)類(lèi)的ClassLoader已經(jīng)被回收
最后,對(duì)該類(lèi)的Class對(duì)象沒(méi)有任何引用
滿足上面三個(gè)條件就可以回收該類(lèi)了。
8、今日思考題
給大家出一個(gè)腦筋急轉(zhuǎn)彎,出題思路有點(diǎn)偏:每個(gè)線程都有Java虛擬機(jī)棧,里面也有方法的局部變量等數(shù)據(jù),這個(gè)Java虛擬機(jī)棧需要進(jìn)行垃圾回收嗎?為什么?
End
版權(quán):公眾號(hào)儒猿技術(shù)窩
未經(jīng)許可不得傳播,如有侵權(quán)將追究法律責(zé)任如何