Java 类的生命周期详解
??????? 最近有位細心的朋友在閱讀筆者的文章時,對Java類的生命周期問題有一些疑惑,筆者打開百度搜了一下相關的問題,看到網上的資料很少有把這個問題講明白的,主要是因為目前國內java方面的教材大多只是告訴你“怎樣做”,但至于“為什么這樣做”卻不多說,所以造成大家在基礎和原理方面的知識比較匱乏,所以筆者今天就斗膽來講一下這個問題,權當拋磚引玉,希望對在這個問題上有疑惑的朋友有所幫助,文中有說的不對的地方,也希望各路高手前來指正。
??????? 首先來了解一下jvm(java虛擬機)中的幾個比較重要的內存區域,這幾個區域在java類的生命周期中扮演著比較重要的角色:
- 方法區:在java的虛擬機中有一塊專門用來存放已經加載的類信息、常量、靜態變量以及方法代碼的內存區域,叫做方法區。
- 常量池:常量池是方法區的一部分,主要用來存放常量和類中的符號引用等信息。
- 堆區:用于存放類的對象實例。
- 棧區:也叫java虛擬機棧,是由一個一個的棧幀組成的后進先出的棧式結構,棧楨中存放方法運行時產生的局部變量、方法出口等信息。當調用一個方法時,虛擬機棧中就會創建一個棧幀存放這些數據,當方法調用完成時,棧幀消失,如果方法中調用了其他方法,則繼續在棧頂創建新的棧楨。
??????? 除了以上四個內存區域之外,jvm中的運行時內存區域還包括本地方法棧和程序計數器,這兩個區域與java類的生命周期關系不是很大,在這里就不說了,感興趣的朋友可以自己百度一下。
?
??????? 當我們編寫一個java的源文件后,經過編譯會生成一個后綴名為class的文件,這種文件叫做字節碼文件,只有這種字節碼文件才能夠在java虛擬機中運行,java類的生命周期就是指一個class文件從加載到卸載的全過程。
??????? 一個java類的完整的生命周期會經歷加載、連接、初始化、使用、卸載五個階段,當然也有在加載或者連接之后沒有被初始化就直接被使用的情況,如圖所示:
下面我們就依次來說一說這五個階段。
(1)加載?????? 在java中,我們經常會接觸到一個詞——類加載,它和這里的加載并不是一回事,通常我們說類加載指的是類的生命周期中加載、連接、初始化三個階段。在加載階段,java虛擬機會做什么工作呢?其實很簡單,就是找到需要加載的類并把類的信息加載到jvm的方法區中,然后在堆區中實例化一個java.lang.Class對象,作為方法區中這個類的信息的入口。
???????類的加載方式比較靈活,我們最常用的加載方式有兩種,一種是根據類的全路徑名找到相應的class文件,然后從class文件中讀取文件內容;另一種是從jar文件中讀取。另外,還有下面幾種方式也比較常用:
- 從網絡中獲取:比如10年前十分流行的Applet。
- 根據一定的規則實時生成,比如設計模式中的動態代理模式,就是根據相應的類自動生成它的代理類。
- 從非class文件中獲取,其實這與直接從class文件中獲取的方式本質上是一樣的,這些非class文件在jvm中運行之前會被轉換為可被jvm所識別的字節碼文件。
?????? 對于加載的時機,各個虛擬機的做法并不一樣,但是有一個原則,就是當jvm“預期”到一個類將要被使用時,就會在使用它之前對這個類進行加載。比如說,在一段代碼中出現了一個類的名字,jvm在執行這段代碼之前并不能確定這個類是否會被使用到,于是,有些jvm會在執行前就加載這個類,而有些則在真正需要用的時候才會去加載它,這取決于具體的jvm實現。我們常用的hotspot虛擬機是采用的后者,就是說當真正用到一個類的時候才對它進行加載。
?????? 加載階段是類的生命周期中的第一個階段,加載階段之后,是連接階段。有一點需要注意,就是有時連接階段并不會等加載階段完全完成之后才開始,而是交叉進行,可能一個類只加載了一部分之后,連接階段就已經開始了。但是這兩個階段總的開始時間和完成時間總是固定的:加載階段總是在連接階段之前開始,連接階段總是在加載階段完成之后完成。
?
(2)連接?????? 連接階段比較復雜,一般會跟加載階段和初始化階段交叉進行,這個階段的主要任務就是做一些加載后的驗證工作以及一些初始化前的準備工作,可以細分為三個步驟:驗證、準備、解析。
- 基本類型(int、long、short、char、byte、boolean、float、double)的默認值為0。
- 引用類型的默認值為null。
- 常量的默認值為我們程序中設定的值,比如我們在程序中定義final static int a = 100,則準備階段中a的初值就是100。
??????? 連接階段完成之后會根據使用的情況(直接引用還是被動引用)來選擇是否對類進行初始化。
?
(3)初始化?????? 如果一個類被直接引用,就會觸發類的初始化。在java中,直接引用的情況有:
- 通過new關鍵字實例化對象、讀取或設置類的靜態變量、調用類的靜態方法。
- 通過反射方式執行以上三種行為。
- 初始化子類的時候,會觸發父類的初始化。
- 作為程序入口直接運行時(也就是直接調用main方法)。
??????? 除了以上四種情況,其他使用類的方式叫做被動引用,而被動引用不會觸發類的初始化。請看主動引用的示例代碼:
[java]?view plaincopy??????? 上面的程序演示了主動引用觸發類的初始化的四種情況。
?
??????? 類的初始化過程是這樣的:按照順序自上而下運行類中的變量賦值語句和靜態語句,如果有父類,則首先按照順序運行父類中的變量賦值語句和靜態語句。先看一個例子,首先建兩個類用來顯示賦值操作:
[java]?view plaincopy下面是演示初始化順序的代碼:
[java]?view plaincopy??????? 上面的代碼中,初始化的順序是:第03行,第05行,第11行,第13行。第04行是聲明操作,沒有賦值,所以不會被運行。而下面的代碼:
[java]?view plaincopy??????? 初始化順序為:第02行、第05行、第10行、第12行,各位可以運行程序查看結果。
???????在類的初始化階段,只會初始化與類相關的靜態賦值語句和靜態語句,也就是有static關鍵字修飾的信息,而沒有static修飾的賦值語句和執行語句在實例化對象的時候才會運行。
?
(4)使用?????? 類的使用包括主動引用和被動引用,主動引用在初始化的章節中已經說過了,下面我們主要來說一下被動引用:
- 引用父類的靜態字段,只會引起父類的初始化,而不會引起子類的初始化。
- 定義類數組,不會引起類的初始化。
- 引用類的常量,不會引起類的初始化。
被動引用的示例代碼:
[java]?view plaincopy
??????? 最后總結一下使用階段:使用階段包括主動引用和被動引用,主動飲用會引起類的初始化,而被動引用不會引起類的初始化。
??????? 當使用階段完成之后,java類就進入了卸載階段。
?
(5)卸載?????? 關于類的卸載,筆者在單例模式討論篇:單例模式與垃圾回收一文中有過描述,在類使用完之后,如果滿足下面的情況,類就會被卸載:
- 該類所有的實例都已經被回收,也就是java堆中不存在該類的任何實例。
- 加載該類的ClassLoader已經被回收。
- 該類對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射訪問該類的方法。
??????? 如果以上三個條件全部滿足,jvm就會在方法區垃圾回收的時候對類進行卸載,類的卸載過程其實就是在方法區中清空類信息,java類的整個生命周期就結束了。
?
三、總 結
?????? ?做java的朋友對于對象的生命周期可能都比較熟悉,對象基本上都是在jvm的堆區中創建,在創建對象之前,會觸發類加載(加載、連接、初始化),當類初始化完成后,根據類信息在堆區中實例化類對象,初始化非靜態變量、非靜態代碼以及默認構造方法,當對象使用完之后會在合適的時候被jvm垃圾收集器回收。讀完本文后我們知道,對象的生命周期只是類的生命周期中使用階段的主動引用的一種情況(即實例化類對象)。而類的整個生命周期則要比對象的生命周期長的多。
參考推薦:
詳解java類的生命周期
C語言編譯全過程剖析
Java 內存模型及GC原理
Android 智能指針原理
from:?http://blog.csdn.net/ithomer/article/details/7536734
總結
以上是生活随笔為你收集整理的Java 类的生命周期详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言编译全过程剖析
- 下一篇: JVM 优点与缺点的深入分析