详解java类的生命周期
引言
??????? 最近有位細(xì)心的朋友在閱讀筆者的文章時(shí),對(duì)Java類的生命周期問(wèn)題有一些疑惑,筆者打開百度搜了一下相關(guān)的問(wèn)題,看到網(wǎng)上的資料很少有把這個(gè)問(wèn)題講明白的,主要是因?yàn)槟壳皣?guó)內(nèi)java方面的教材大多只是告訴你“怎樣做”,但至于“為什么這樣做”卻不多說(shuō),所以造成大家在基礎(chǔ)和原理方面的知識(shí)比較匱乏,所以筆者今天就斗膽來(lái)講一下這個(gè)問(wèn)題,權(quán)當(dāng)拋磚引玉,希望對(duì)在這個(gè)問(wèn)題上有疑惑的朋友有所幫助,文中有說(shuō)的不對(duì)的地方,也希望各路高手前來(lái)指正。
??????? 首先來(lái)了解一下jvm(java虛擬機(jī))中的幾個(gè)比較重要的內(nèi)存區(qū)域,這幾個(gè)區(qū)域在java類的生命周期中扮演著比較重要的角色:
- 方法區(qū):在java的虛擬機(jī)中有一塊專門用來(lái)存放已經(jīng)加載的類信息、常量、靜態(tài)變量以及方法代碼的內(nèi)存區(qū)域,叫做方法區(qū)。
- 常量池:常量池是方法區(qū)的一部分,主要用來(lái)存放常量和類中的符號(hào)引用等信息。
- 堆區(qū):用于存放類的對(duì)象實(shí)例。
- 棧區(qū):也叫java虛擬機(jī)棧,是由一個(gè)一個(gè)的棧幀組成的后進(jìn)先出的棧式結(jié)構(gòu),棧楨中存放方法運(yùn)行時(shí)產(chǎn)生的局部變量、方法出口等信息。當(dāng)調(diào)用一個(gè)方法時(shí),虛擬機(jī)棧中就會(huì)創(chuàng)建一個(gè)棧幀存放這些數(shù)據(jù),當(dāng)方法調(diào)用完成時(shí),棧幀消失,如果方法中調(diào)用了其他方法,則繼續(xù)在棧頂創(chuàng)建新的棧楨。
??????? 除了以上四個(gè)內(nèi)存區(qū)域之外,jvm中的運(yùn)行時(shí)內(nèi)存區(qū)域還包括本地方法棧和程序計(jì)數(shù)器,這兩個(gè)區(qū)域與java類的生命周期關(guān)系不是很大,在這里就不說(shuō)了,感興趣的朋友可以自己百度一下。
?
類的生命周期
??????? 當(dāng)我們編寫一個(gè)java的源文件后,經(jīng)過(guò)編譯會(huì)生成一個(gè)后綴名為class的文件,這種文件叫做字節(jié)碼文件,只有這種字節(jié)碼文件才能夠在java虛擬機(jī)中運(yùn)行,java類的生命周期就是指一個(gè)class文件從加載到卸載的全過(guò)程。
??????? 一個(gè)java類的完整的生命周期會(huì)經(jīng)歷加載、連接、初始化、使用、和卸載五個(gè)階段,當(dāng)然也有在加載或者連接之后沒(méi)有被初始化就直接被使用的情況,如圖所示:
?
?
下面我們就依次來(lái)說(shuō)一說(shuō)這五個(gè)階段。
?
加載
?????? 在java中,我們經(jīng)常會(huì)接觸到一個(gè)詞——類加載,它和這里的加載并不是一回事,通常我們說(shuō)類加載指的是類的生命周期中加載、連接、初始化三個(gè)階段。在加載階段,java虛擬機(jī)會(huì)做什么工作呢?其實(shí)很簡(jiǎn)單,就是找到需要加載的類并把類的信息加載到j(luò)vm的方法區(qū)中,然后在堆區(qū)中實(shí)例化一個(gè)java.lang.Class對(duì)象,作為方法區(qū)中這個(gè)類的信息的入口。
???????類的加載方式比較靈活,我們最常用的加載方式有兩種,一種是根據(jù)類的全路徑名找到相應(yīng)的class文件,然后從class文件中讀取文件內(nèi)容;另一種是從jar文件中讀取。另外,還有下面幾種方式也比較常用:
- 從網(wǎng)絡(luò)中獲取:比如10年前十分流行的Applet。
- 根據(jù)一定的規(guī)則實(shí)時(shí)生成,比如設(shè)計(jì)模式中的動(dòng)態(tài)代理模式,就是根據(jù)相應(yīng)的類自動(dòng)生成它的代理類。
- 從非class文件中獲取,其實(shí)這與直接從class文件中獲取的方式本質(zhì)上是一樣的,這些非class文件在jvm中運(yùn)行之前會(huì)被轉(zhuǎn)換為可被jvm所識(shí)別的字節(jié)碼文件。
?????? 對(duì)于加載的時(shí)機(jī),各個(gè)虛擬機(jī)的做法并不一樣,但是有一個(gè)原則,就是當(dāng)jvm“預(yù)期”到一個(gè)類將要被使用時(shí),就會(huì)在使用它之前對(duì)這個(gè)類進(jìn)行加載。比如說(shuō),在一段代碼中出現(xiàn)了一個(gè)類的名字,jvm在執(zhí)行這段代碼之前并不能確定這個(gè)類是否會(huì)被使用到,于是,有些jvm會(huì)在執(zhí)行前就加載這個(gè)類,而有些則在真正需要用的時(shí)候才會(huì)去加載它,這取決于具體的jvm實(shí)現(xiàn)。我們常用的hotspot虛擬機(jī)是采用的后者,就是說(shuō)當(dāng)真正用到一個(gè)類的時(shí)候才對(duì)它進(jìn)行加載。
?????? 加載階段是類的生命周期中的第一個(gè)階段,加載階段之后,是連接階段。有一點(diǎn)需要注意,就是有時(shí)連接階段并不會(huì)等加載階段完全完成之后才開始,而是交叉進(jìn)行,可能一個(gè)類只加載了一部分之后,連接階段就已經(jīng)開始了。但是這兩個(gè)階段總的開始時(shí)間和完成時(shí)間總是固定的:加載階段總是在連接階段之前開始,連接階段總是在加載階段完成之后完成。
?
連接
?????? 連接階段比較復(fù)雜,一般會(huì)跟加載階段和初始化階段交叉進(jìn)行,這個(gè)階段的主要任務(wù)就是做一些加載后的驗(yàn)證工作以及一些初始化前的準(zhǔn)備工作,可以細(xì)分為三個(gè)步驟:驗(yàn)證、準(zhǔn)備和解析。
- 基本類型(int、long、short、char、byte、boolean、float、double)的默認(rèn)值為0。
- 引用類型的默認(rèn)值為null。
- 常量的默認(rèn)值為我們程序中設(shè)定的值,比如我們?cè)诔绦蛑卸xfinal static int a = 100,則準(zhǔn)備階段中a的初值就是100。
??????? 連接階段完成之后會(huì)根據(jù)使用的情況(直接引用還是被動(dòng)引用)來(lái)選擇是否對(duì)類進(jìn)行初始化。
?
初始化
?????? 如果一個(gè)類被直接引用,就會(huì)觸發(fā)類的初始化。在java中,直接引用的情況有:
- 通過(guò)new關(guān)鍵字實(shí)例化對(duì)象、讀取或設(shè)置類的靜態(tài)變量、調(diào)用類的靜態(tài)方法。
- 通過(guò)反射方式執(zhí)行以上三種行為。
- 初始化子類的時(shí)候,會(huì)觸發(fā)父類的初始化。
- 作為程序入口直接運(yùn)行時(shí)(也就是直接調(diào)用main方法)。
??????? 除了以上四種情況,其他使用類的方式叫做被動(dòng)引用,而被動(dòng)引用不會(huì)觸發(fā)類的初始化。請(qǐng)看主動(dòng)引用的示例代碼:
[java]?view plaincopy??????? 上面的程序演示了主動(dòng)引用觸發(fā)類的初始化的四種情況。
?
??????? 類的初始化過(guò)程是這樣的:按照順序自上而下運(yùn)行類中的變量賦值語(yǔ)句和靜態(tài)語(yǔ)句,如果有父類,則首先按照順序運(yùn)行父類中的變量賦值語(yǔ)句和靜態(tài)語(yǔ)句。先看一個(gè)例子,首先建兩個(gè)類用來(lái)顯示賦值操作:
[java]?view plaincopy下面是演示初始化順序的代碼:
[java]?view plaincopy??????? 上面的代碼中,初始化的順序是:第03行,第05行,第11行,第13行。第04行是聲明操作,沒(méi)有賦值,所以不會(huì)被運(yùn)行。而下面的代碼:
[java]?view plaincopy??????? 初始化順序?yàn)?#xff1a;第02行、第05行、第10行、第12行,各位可以運(yùn)行程序查看結(jié)果。
???????在類的初始化階段,只會(huì)初始化與類相關(guān)的靜態(tài)賦值語(yǔ)句和靜態(tài)語(yǔ)句,也就是有static關(guān)鍵字修飾的信息,而沒(méi)有static修飾的賦值語(yǔ)句和執(zhí)行語(yǔ)句在實(shí)例化對(duì)象的時(shí)候才會(huì)運(yùn)行。
?
使用
?????? 類的使用包括主動(dòng)引用和被動(dòng)引用,主動(dòng)引用在初始化的章節(jié)中已經(jīng)說(shuō)過(guò)了,下面我們主要來(lái)說(shuō)一下被動(dòng)引用:
- 引用父類的靜態(tài)字段,只會(huì)引起父類的初始化,而不會(huì)引起子類的初始化。
- 定義類數(shù)組,不會(huì)引起類的初始化。
- 引用類的常量,不會(huì)引起類的初始化。
被動(dòng)引用的示例代碼:
[java]?view plaincopy
??????? 最后總結(jié)一下使用階段:使用階段包括主動(dòng)引用和被動(dòng)引用,主動(dòng)飲用會(huì)引起類的初始化,而被動(dòng)引用不會(huì)引起類的初始化。
??????? 當(dāng)使用階段完成之后,java類就進(jìn)入了卸載階段。
?
卸載
?????? 關(guān)于類的卸載,筆者在單例模式討論篇:單例模式與垃圾回收一文中有過(guò)描述,在類使用完之后,如果滿足下面的情況,類就會(huì)被卸載:
- 該類所有的實(shí)例都已經(jīng)被回收,也就是java堆中不存在該類的任何實(shí)例。
- 加載該類的ClassLoader已經(jīng)被回收。
- 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法。
??????? 如果以上三個(gè)條件全部滿足,jvm就會(huì)在方法區(qū)垃圾回收的時(shí)候?qū)︻愡M(jìn)行卸載,類的卸載過(guò)程其實(shí)就是在方法區(qū)中清空類信息,java類的整個(gè)生命周期就結(jié)束了。
?
總結(jié)
?????? ?做java的朋友對(duì)于對(duì)象的生命周期可能都比較熟悉,對(duì)象基本上都是在jvm的堆區(qū)中創(chuàng)建,在創(chuàng)建對(duì)象之前,會(huì)觸發(fā)類加載(加載、連接、初始化),當(dāng)類初始化完成后,根據(jù)類信息在堆區(qū)中實(shí)例化類對(duì)象,初始化非靜態(tài)變量、非靜態(tài)代碼以及默認(rèn)構(gòu)造方法,當(dāng)對(duì)象使用完之后會(huì)在合適的時(shí)候被jvm垃圾收集器回收。讀完本文后我們知道,對(duì)象的生命周期只是類的生命周期中使用階段的主動(dòng)引用的一種情況(即實(shí)例化類對(duì)象)。而類的整個(gè)生命周期則要比對(duì)象的生命周期長(zhǎng)的多。
?
如果有疑問(wèn)或者發(fā)現(xiàn)本文中不對(duì)的地方,歡迎各位留言。本文doc文檔版本的下載地址:
csdn資源:http://download.csdn.net/detail/zhengzhb/4260878
?百度文庫(kù):http://wenku.baidu.com/view/5d2c8b6858fafab069dc023d.html?st=1
from:?http://blog.csdn.net/zhengzhb/article/details/7517213
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的详解java类的生命周期的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java 数组转型和范型
- 下一篇: Mockito:一个强大的用于Java开