日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java类的完整生命周期详解

發(fā)布時(shí)間:2025/3/19 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java类的完整生命周期详解 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

一、概述

二、過程一:Loading(加載)階段

1、加載完成的操作

(1)加載的理解

(2)加載完成的操作

2、二進(jìn)制流的獲取方式

3、類模型與Class實(shí)例的位置

(1)類模型的位置

(2)Class實(shí)例的位置

(3)圖示

(4)再說明

4、數(shù)組類的加載

三、過程二:Linking(鏈接)階段

1、環(huán)節(jié)1:鏈接階段之Verification(驗(yàn)證)

整體說明:

具體說明:

2、環(huán)節(jié)2:鏈接階段之Preparation(準(zhǔn)備)

3、環(huán)節(jié)3:鏈接階段之Resolution(解析)

(1)具體描述:

(2)小結(jié)

(3)字符串的復(fù)習(xí)

四、過程三:Initialization(初始化)階段

1、static與final的搭配問題

2、()的線程安全性

3、類的初始化情況:主動(dòng)使用vs被動(dòng)使用

(1)主動(dòng)使用

(2)被動(dòng)使用

五、過程四:類的Using(使用)

六、過程五:類的Unloading(卸載)

(1)類、類的加載器、類的實(shí)例之間的引用關(guān)系

(2)類的聲明周期

(3)實(shí)例

(4)類的卸載

(5)方法區(qū)的垃圾回收


一、概述

? ? 在Java中數(shù)據(jù)類型分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。基本數(shù)據(jù)類型由虛擬機(jī)預(yù)先定義,引用數(shù)據(jù)類型則需要進(jìn)行類的加載。

? ? 按照J(rèn)ava虛擬機(jī)規(guī)范,從class文件到加載到內(nèi)存中的類,到類卸載出內(nèi)存為止,它的整個(gè)生命周期包括如下7個(gè)階段:

?

? ? 其中,驗(yàn)證、驗(yàn)證、準(zhǔn)備、解析3個(gè)部分統(tǒng)稱為鏈接(Linking)。

? ? 從程序中類的使用過程看:

?

二、過程一:Loading(加載)階段

1、加載完成的操作

(1)加載的理解

? ? 所謂加載,簡而言之就是將Java類的字節(jié)碼文件加載到機(jī)器內(nèi)存中,并在內(nèi)存中構(gòu)建出Java類的原型——類模板對(duì)象。所謂類模板對(duì)象,其實(shí)就是Java類在JVM內(nèi)存中的一個(gè)快照,JVM將字節(jié)碼文件中解析出的常量池、類字段、類方法等信息存儲(chǔ)到類模板中,這樣JVM在運(yùn)行期便能通過類模板而獲取Java類中的任何信息,能夠?qū)ava類的成員變量進(jìn)行遍歷,也能進(jìn)行Java方法的調(diào)用。

? ? 反射的機(jī)制即基于這一基礎(chǔ)。如果JVM沒有將Java類的聲明信息存儲(chǔ)起來,則JVM在運(yùn)行期也無法反射。

(2)加載完成的操作

? ? 加載階段,簡言之,查找并加載類的二進(jìn)制數(shù)據(jù),生成Class的實(shí)例。

? ? 在加載類時(shí),Java虛擬機(jī)必須完成以下3件事情:

·?通過類的全名,獲取類的二進(jìn)制數(shù)據(jù)流。

·?解析類的二進(jìn)制數(shù)據(jù)流為方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)(Java類模型)。

·?創(chuàng)建java.lang.Class類的實(shí)例,表示該類型。作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口。

2、二進(jìn)制流的獲取方式

? ? 對(duì)于類的二進(jìn)制數(shù)據(jù)流,虛擬機(jī)可以通過多種途徑產(chǎn)生或獲得。(只要所讀取的字節(jié)碼符合JVM規(guī)范即可)

·?虛擬機(jī)可能通過文件系統(tǒng)讀入一個(gè)class后綴的文件(最常見)

·?讀入jar、zip等歸檔數(shù)據(jù)包,提取類文件。

·?事先存放在數(shù)據(jù)庫中的類的二進(jìn)制數(shù)據(jù)。

·?使用類似于HTTP之類的協(xié)議通過網(wǎng)絡(luò)進(jìn)行加載。

·?在運(yùn)行時(shí)生成一段Class的二進(jìn)制信息等。

在獲取到類的二進(jìn)制信息后,Java虛擬機(jī)就會(huì)處理這些數(shù)據(jù),并最終轉(zhuǎn)為一個(gè)java.lang.Class的實(shí)例。

如果輸入數(shù)據(jù)不是ClassFile的結(jié)構(gòu),則會(huì)拋出ClassFormatError。

3、類模型與Class實(shí)例的位置

(1)類模型的位置

? ? 加載的類在JVM中創(chuàng)建相應(yīng)的類結(jié)構(gòu),類結(jié)構(gòu)會(huì)存儲(chǔ)在方法區(qū)(JDK1.8之前:永久代;JDK1.8及以后:元空間)。

(2)Class實(shí)例的位置

? ? 類將.class文件加載至元空間后,會(huì)在堆中創(chuàng)建一個(gè)java.lang.Class對(duì)象,用來封裝類位于方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu),該Class對(duì)象是在加載類的過程中創(chuàng)建的,每個(gè)類都對(duì)應(yīng)有一個(gè)Class類型的對(duì)象。

(3)圖示

?

? ? 外部可以通過訪問代表Order類的Class對(duì)象來獲取Order的類數(shù)據(jù)結(jié)構(gòu)。

(4)再說明

? ? Class類的構(gòu)造方法是私有的,只有JVM能夠創(chuàng)建。

? ? java.lang.Class實(shí)例是訪問類型元數(shù)據(jù)的接口,也是實(shí)現(xiàn)反射的關(guān)鍵數(shù)據(jù)、入口。通過Class類提供的接口,可以獲得目標(biāo)類所關(guān)聯(lián)的.class文件中具體的數(shù)據(jù)結(jié)構(gòu):方法、字段等信息。

import java.lang.reflect.Method; import java.lang.reflect.Modifier;public class TestString {public static void main(String[] args) {try {Class clazz = Class.forName("java.lang.String");Method[] ms = clazz.getDeclaredMethods();for (Method m : ms) {// 獲取方法修飾符String mod = Modifier.toString(m.getModifiers());System.out.print(mod + " ");// 獲取方法的返回值類型String returnType = m.getReturnType().getSimpleName();System.out.print(returnType + " ");// 獲取方法名System.out.print(m.getName() + "(");// 獲取方法的參數(shù)列表Class<?>[] ps = m.getParameterTypes();if(ps.length == 0) System.out.print(")");for (int i = 0;i < ps.length; i++){char end = (i == ps.length -1) ? ')' : ',';// 獲取參數(shù)的類型System.out.print(ps[i].getSimpleName() + end);}System.out.println();}} catch (ClassNotFoundException e) {e.printStackTrace();}} }public boolean equals(Object) public String toString() public int hashCode() public int compareTo(String) public volatile int compareTo(Object) public int indexOf(String,int) public int indexOf(String) public int indexOf(int,int) public int indexOf(int) static int indexOf(char[],int,int,char[],int,int,int) static int indexOf(char[],int,int,String,int) public static String valueOf(int) public static String valueOf(long) public static String valueOf(float) public static String valueOf(boolean) public static String valueOf(char[]) public static String valueOf(char[],int,int) public static String valueOf(Object) public static String valueOf(char) public static String valueOf(double) public char charAt(int) private static void checkBounds(byte[],int,int) public int codePointAt(int) public int codePointBefore(int) public int codePointCount(int,int) public int compareToIgnoreCase(String) public String concat(String) public boolean contains(CharSequence) public boolean contentEquals(CharSequence) public boolean contentEquals(StringBuffer) public static String copyValueOf(char[]) public static String copyValueOf(char[],int,int) public boolean endsWith(String) public boolean equalsIgnoreCase(String) public static transient String format(Locale,String,Object[]) public static transient String format(String,Object[]) public void getBytes(int,int,byte[],int) public byte[] getBytes(Charset) public byte[] getBytes(String) public byte[] getBytes() public void getChars(int,int,char[],int) void getChars(char[],int) private int indexOfSupplementary(int,int) public native String intern() public boolean isEmpty() public static transient String join(CharSequence,CharSequence[]) public static String join(CharSequence,Iterable) public int lastIndexOf(int) public int lastIndexOf(String) static int lastIndexOf(char[],int,int,String,int) public int lastIndexOf(String,int) public int lastIndexOf(int,int) static int lastIndexOf(char[],int,int,char[],int,int,int) private int lastIndexOfSupplementary(int,int) public int length() public boolean matches(String) private boolean nonSyncContentEquals(AbstractStringBuilder) public int offsetByCodePoints(int,int) public boolean regionMatches(int,String,int,int) public boolean regionMatches(boolean,int,String,int,int) public String replace(char,char) public String replace(CharSequence,CharSequence) public String replaceAll(String,String) public String replaceFirst(String,String) public String[] split(String) public String[] split(String,int) public boolean startsWith(String,int) public boolean startsWith(String) public CharSequence subSequence(int,int) public String substring(int) public String substring(int,int) public char[] toCharArray() public String toLowerCase(Locale) public String toLowerCase() public String toUpperCase() public String toUpperCase(Locale) public String trim()

4、數(shù)組類的加載

? ? 創(chuàng)建數(shù)組類的情況稍微有些特殊,因?yàn)?span style="color:#fe2c24;">數(shù)組類本身并不是由類加載器負(fù)責(zé)創(chuàng)建,而是由JVM在運(yùn)行時(shí)根據(jù)需要而直接創(chuàng)建的,但數(shù)組的元素類型仍然需要依靠類加載器去創(chuàng)建。創(chuàng)建數(shù)組類(下述簡稱A)的過程:

①?如果數(shù)組的元素類型是引用類型,那么就遵循定義的加載過程遞歸加載和創(chuàng)建數(shù)組A的元素類型;

②?JVM使用指定的元素類型和數(shù)組維度來創(chuàng)建新的數(shù)組類。

? ? 如果數(shù)組的元素類型是引用類型,數(shù)組類的可訪問性就由元素類型的可訪問性決定。否則數(shù)組類的可訪問性將被缺省定義為public。

三、過程二:Linking(鏈接)階段

1、環(huán)節(jié)1:鏈接階段之Verification(驗(yàn)證)

? ? 當(dāng)類加載到系統(tǒng)后,就開始鏈接操作,驗(yàn)證是鏈接操作的第一步。

? ? 它的目的是保證加載的字節(jié)碼是合法的、合理并符合規(guī)范的

? ? 驗(yàn)證的步驟比較復(fù)雜,實(shí)際要驗(yàn)證的項(xiàng)目也很繁多,大體上Java虛擬機(jī)需要做以下檢查,如圖所示。

?

整體說明:

? ? 驗(yàn)證的內(nèi)容則涵蓋了類數(shù)據(jù)信息的格式驗(yàn)證、語義檢查、字節(jié)碼驗(yàn)證,以及符號(hào)引用驗(yàn)證等。

·?其中格式驗(yàn)證會(huì)和加載階段一起執(zhí)行。驗(yàn)證通過之后,類加載器才會(huì)成功將類的二進(jìn)制數(shù)據(jù)信息加載到方法區(qū)中。

·?格式驗(yàn)證之外的驗(yàn)證操作將會(huì)在方法區(qū)中進(jìn)行。

? ??

? ? 鏈接階段的驗(yàn)證雖然拖慢了加載速度,但是它避免了在字節(jié)碼運(yùn)行時(shí)還需要進(jìn)行各種檢查。(磨刀不誤砍柴工)

具體說明:

①?格式驗(yàn)證:是否以魔數(shù)0xCAFEBABE開頭,主版本和副版本號(hào)是否在當(dāng)前Java虛擬機(jī)的支持范圍內(nèi),數(shù)據(jù)中每一個(gè)項(xiàng)是否都擁有正確的長度等。

②?Java虛擬機(jī)會(huì)進(jìn)行字節(jié)碼的語義檢查,但凡在語義上不符合規(guī)范的,虛擬機(jī)也不會(huì)給予驗(yàn)證通過。比如:

·?是否所有的類都有父類的存在(在Java里,除了Object外,其他類都應(yīng)該有父類)

·?是否一些被定義為final的方法或者類被重寫或繼承了。

·?非抽象類是否實(shí)現(xiàn)了所有抽象方法或者接口方法。

·?是否存在不兼容的方法(比如方法的簽名除了返回值不同,其他都一樣,這種方法會(huì)讓虛擬機(jī)無從下手調(diào)度;abstract情況下的方法,就不能是final的了)

③?Java虛擬機(jī)還會(huì)進(jìn)行字節(jié)碼驗(yàn)證,字節(jié)碼驗(yàn)證也是驗(yàn)證過程中最為復(fù)雜的一個(gè)過程。它試圖通過對(duì)字節(jié)碼流的分析,判斷字節(jié)碼是否可以被正確地執(zhí)行。比如:

·?在字節(jié)碼的執(zhí)行過程中,是否會(huì)跳轉(zhuǎn)到一條不存在的指令。

·?函數(shù)的調(diào)用是否傳遞了正確類型的參數(shù)。

·?變量的賦值是不是給了正確的數(shù)據(jù)類型等。

? ? 棧映射幀(StackMapTable)就是在這個(gè)階段,用于檢測在特定的字節(jié)碼處,其局部變量表和操作數(shù)棧是否有著正確的數(shù)據(jù)類型。但遺憾的是,100%準(zhǔn)確地判斷一段字節(jié)碼是否可以被安全執(zhí)行是無法實(shí)現(xiàn)的,因此,該過程只是盡可能地檢查出可以預(yù)知的明顯的問題。如果在這個(gè)階段無法通過檢查,虛擬機(jī)也不會(huì)正確裝載這個(gè)類。但是,如果通過了這個(gè)階段的檢查,也不能說明這個(gè)類是完全沒有問題的。

在前面3次檢查中,已經(jīng)排除了文件格式錯(cuò)誤、語義錯(cuò)誤以及字節(jié)碼的不正確性。但是依然不能確保類是沒有問題的

④?校驗(yàn)器還將進(jìn)行符號(hào)引用的驗(yàn)證。Class文件在其常量池會(huì)通過字符串記錄自己將要使用的其他類或者方法。因此,在驗(yàn)證階段,虛擬機(jī)就會(huì)檢查這些類或者方法確實(shí)是存在的,并且當(dāng)前類有權(quán)限訪問這些數(shù)據(jù),如果一個(gè)需要使用類無法在系統(tǒng)中找到,則會(huì)拋出NoClassDefFoundError,如果一個(gè)方法無法被找到,則會(huì)拋出NoSuchMethodError。

2、環(huán)節(jié)2:鏈接階段之Preparation(準(zhǔn)備)

? ? 準(zhǔn)備階段(Preparation),簡言之,為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值。

? ? 當(dāng)一個(gè)類驗(yàn)證通過時(shí),虛擬機(jī)就會(huì)進(jìn)入準(zhǔn)備階段。在這個(gè)階段,虛擬機(jī)就會(huì)為這個(gè)類分配相應(yīng)的內(nèi)存空間,并設(shè)置默認(rèn)初始值。Java虛擬機(jī)為各類型變量默認(rèn)的初始值如表所示。

類型

默認(rèn)初始值

byte

(byte)0

short

(short)0

int

0

long

0L

float

0.0f

double

0.0

char

\u0000

boolean

false

reference

null

注意:Java并不支持boolean類型,對(duì)于boolean類型,內(nèi)部實(shí)現(xiàn)是int,由于int的默認(rèn)值是0,故對(duì)應(yīng)的,boolean的默認(rèn)值就是false。

注意:

①?這里不包含基本數(shù)據(jù)類型的字段用static?final修飾的情況,因?yàn)閒inal在編譯的時(shí)候就會(huì)分配了,準(zhǔn)備階段會(huì)顯式賦值。

②?注意這里不會(huì)為實(shí)例變量分配初始化,類變量會(huì)分配在方法區(qū)中,而實(shí)例變量是會(huì)隨著對(duì)象一起分配到Java堆中。

③?在這個(gè)階段并不會(huì)像初始化階段中那樣會(huì)有初始化或者代碼被執(zhí)行。

3、環(huán)節(jié)3:鏈接階段之Resolution(解析)

? ? 在準(zhǔn)備階段完成后,就進(jìn)入了解析階段,

? ? 解析階段(Resolution),簡言之,將類、接口、字段和方法的符號(hào)引用轉(zhuǎn)為直接引用。

(1)具體描述:

? ? 符號(hào)引用就是一些字面量的引用,和虛擬機(jī)的內(nèi)部數(shù)據(jù)結(jié)構(gòu)和內(nèi)存布局無關(guān)。比較容易理解的就是在Class類文件中,通過常量池進(jìn)行了大量的符號(hào)引用。但是在程序?qū)嶋H運(yùn)行時(shí),只有符號(hào)引用是不夠的,比如當(dāng)下println()方法被調(diào)用時(shí),系統(tǒng)需要明確知道該方法的位置。

舉例:輸出操作System.out.println()對(duì)應(yīng)的字節(jié)碼:

invokevirtual #24 <java/io/PrintStream.println>

?

? ? 以方法為例,Java虛擬機(jī)為每個(gè)類都準(zhǔn)備了一張方法表,將其所有的方法都列在表中,當(dāng)需要調(diào)用一個(gè)類的方法的時(shí)候,只要知道這個(gè)方法在方法表中的偏移量就可以直接調(diào)用該方法。通過解析操作,符號(hào)引用就可以轉(zhuǎn)變?yōu)槟繕?biāo)方法在類中方法表中的位置,從而使得方法被成功調(diào)用。

(2)小結(jié)

? ? 所謂解析就是將符號(hào)引用轉(zhuǎn)為直接引用,也就是得到類、字段、方法在內(nèi)存中的指針或者偏移量。因此,可以說,如果直接引用存在,那么可以肯定系統(tǒng)中存在該類、方法或者字段。但只存在符號(hào)引用,不能確定系統(tǒng)中一定存在該結(jié)構(gòu)。

? ? 不過Java虛擬機(jī)規(guī)范并沒有明確要求解析階段一定要按照順序執(zhí)行。在HotSpot?VM中,加載、驗(yàn)證、準(zhǔn)備和初始化會(huì)按照順序有條不紊地執(zhí)行,但在鏈接階段中的解析操作往往會(huì)伴隨著JVM在執(zhí)行完初始化之后再執(zhí)行。

(3)字符串的復(fù)習(xí)

? ? 最后,再來看一下CONSTANT_String的解析。由于字符串在程序開發(fā)中有著重要的作用,因此,讀者有必要了解一下String在Java虛擬機(jī)中的處理。當(dāng)在Java代碼中直接使用字符串常量時(shí),就會(huì)在類中出現(xiàn)CONSTANT_String,它表示字符串常量,并且會(huì)引用一個(gè)CONSTANT_UTF8的常量項(xiàng)。在Java虛擬機(jī)內(nèi)部運(yùn)行中的常量池中,會(huì)維護(hù)一張字符串拘留表(intern),它會(huì)保存所有出現(xiàn)過的字符串常量,并且沒有重復(fù)項(xiàng)。只要以CONSTANT_String形式出現(xiàn)的字符串也都會(huì)在這張表中。使用String.intern()方法可以得到一個(gè)字符串在拘留表中的引用,因?yàn)樵摫碇袥]有重復(fù)項(xiàng),所以任何字面相同的字符串的String.intern()方法返回總是相等的。

四、過程三:Initialization(初始化)階段

? ? 初始化階段,簡言之,為類的靜態(tài)變量賦予正確的初始值。

(1)?具體描述

? ? 類的初始化是類裝載的最后一個(gè)階段。如果前面的步驟都沒有問題,那么表示類可以順利裝載到系統(tǒng)中。此時(shí),類才會(huì)開始執(zhí)行Java字節(jié)碼。(即:到了初始化階段,才真正開始執(zhí)行類中定義的Java程序代碼

? ? 初始化階段的重要工作是執(zhí)行類的初始化方法:<clinit>()方法。

·?該方法僅能由Java編譯器生成并由JVM調(diào)用,程序開發(fā)者無法自定義一個(gè)同名的方法,更無法直接在Java程序中調(diào)用該方法,雖然該方法也是由字節(jié)碼指令所組成。

·?它是由靜態(tài)成員的賦值語句以及static語句塊合并產(chǎn)生的。

(2)說明

①?在加載一個(gè)類之前,虛擬機(jī)總是會(huì)試圖加載該類的父類,因此父類的<clinit>總是在子類<clinit>之前被調(diào)用。也就是說,父類的static塊優(yōu)先級(jí)高于子類。

②?Java編譯器并不會(huì)為所有的類都產(chǎn)生<clinit>()初始化方法。哪些類在編譯為字節(jié)碼后,字節(jié)碼文件中將不會(huì)包含<clinit>()方法?

·?一個(gè)類中并沒有聲明任何的類變量,也沒有靜態(tài)代碼塊時(shí)。

·?一個(gè)類中聲明類變量,但是沒有明確使用類變量的初始化語句以及靜態(tài)代碼塊來執(zhí)行初始化操作時(shí)。

·?一個(gè)類中包含static?final修飾的基本數(shù)據(jù)類型的字段,這些類字段初始化語句采用編譯時(shí)常量表達(dá)式。

public class InitializationTest1 {// 場景1:對(duì)應(yīng)非靜態(tài)的字段,不管是否進(jìn)行了顯式賦值,都不會(huì)生成<clinit>()方法public int num = 1;//場景2:靜態(tài)的字段,沒有顯式的賦值,不會(huì)生成<clinit>()方法public static int num1;//場景3:對(duì)于聲明為static final的基本數(shù)據(jù)類型字段,不管是否進(jìn)行顯式賦值,都不會(huì)生成<clinit>()方法。而是在鏈接階段的準(zhǔn)備環(huán)節(jié)賦值。public static final int num2 = 1;// 會(huì)生成<clinit>()方法public static int num2 = 1;}

1、static與final的搭配問題

/ ** 說明:使用static + final修飾的字段的顯式賦值的操作,到底是在哪個(gè)階段進(jìn)行的賦值? 情況1:在鏈接階段的準(zhǔn)備環(huán)節(jié)賦值 情況2:在初始化階段<clinit>()方法賦值總結(jié): 在鏈接階段的準(zhǔn)備環(huán)節(jié)賦值的情況: 1.對(duì)于基本數(shù)據(jù)類型的字段來說,如果使用static final修飾,則顯式賦值(直接賦值常量,而非調(diào)用方法)通常是在鏈接階段的準(zhǔn)備環(huán)節(jié)進(jìn)行。 2.對(duì)于String來說,如果使用字面量的方式賦值,使用static final修飾的話,則顯式賦值通常是在鏈接階段的準(zhǔn)備環(huán)節(jié)進(jìn)行。在初始化階段<clinit>()方法賦值的情況: 1.排除上述在準(zhǔn)備環(huán)節(jié)賦值的情況之外的情況。最終結(jié)論:使用static + final修飾,且顯式賦值中不涉及到方法或構(gòu)造器調(diào)用的基本數(shù)據(jù)類型或String類型的顯式賦值,是在鏈接階段的準(zhǔn)備環(huán)節(jié)進(jìn)行。 */ public class InitializationTest2 {public static int a = 1; // 在初始化階段<clinit>()方法賦值public static final int INT_CONSTANT = 10; // 在鏈接階段的準(zhǔn)備環(huán)節(jié)賦值public static final int NUM1 = new Random().nextInt(10); // 在初始化階段<clinit>()方法賦值public static final Integer INTEGER_CONSTANT1 = Integer.valueOf(100); // 在初始化階段<clinit>()方法賦值public static Integer INTEGER_CONSTANT2 = Integer.valueOf(1000); // 在初始化階段<clinit>()方法賦值public static final String s0 = "helloworld";// 在鏈接階段的準(zhǔn)備環(huán)節(jié)賦值public static final String s = new String("helloworld"); // 在初始化階段<clinit>()方法賦值public static String s0 = "helloworld";// 在初始化階段<clinit>()方法賦值}

?

2、<clinit>()的線程安全性

? ? 對(duì)于<clinit>()方法的調(diào)用,也就是類的初始化,虛擬機(jī)會(huì)在內(nèi)部確保其多線程環(huán)境中的安全性。

? ? 虛擬機(jī)會(huì)保證一個(gè)類的<clinit>()方法在多線程環(huán)境中被正確地加鎖、同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的<clinit>()方法,其他線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行<clinit>()方法完畢。

? ? 正是因?yàn)楹瘮?shù)<clinit>()帶鎖線程安全的,因此,如果在一個(gè)類的<clinit>()方法中有耗時(shí)很長的操作,就可能造成多個(gè)線程阻塞,引發(fā)死鎖。并且這種死鎖是很難發(fā)現(xiàn)的,因?yàn)榭雌饋硭鼈儾]有可用的鎖信息。

? ? 如果之前的線程成功加載了類,則等在隊(duì)列中的線程就沒有機(jī)會(huì)再執(zhí)行<clinit>()方法了。那么,當(dāng)需要使用這個(gè)類時(shí),虛擬機(jī)會(huì)直接返回給它已經(jīng)準(zhǔn)備好的信息。

3、類的初始化情況:主動(dòng)使用vs被動(dòng)使用

? ? Java程序?qū)︻惖氖褂梅譃閮煞N:主動(dòng)使用?和?被動(dòng)使用。

? ? 使用-XX:+TraceClassLoading查看類的加載順序。

(1)主動(dòng)使用

? ? Class只有在必須要首次使用的時(shí)候才會(huì)被裝載,Java虛擬機(jī)不會(huì)無條件地裝載Class類型。Java虛擬機(jī)規(guī)定,一個(gè)類或接口在初次使用前,必須要進(jìn)行初始化。這里指的“使用”,是指主動(dòng)使用,主動(dòng)使用只有下列幾種情況:(即:如果出現(xiàn)如下的情況,則會(huì)對(duì)類進(jìn)行初始化操作。而初始化操作之前的加載、驗(yàn)證、準(zhǔn)備已經(jīng)完成。)

①?當(dāng)創(chuàng)建一個(gè)類的實(shí)例時(shí),比如使用new關(guān)鍵字,或者通過反射、克隆、反序列化。

②?當(dāng)調(diào)用類的靜態(tài)方法時(shí),即當(dāng)使用了字節(jié)碼invokestatic指令。

③?當(dāng)使用類、接口的靜態(tài)字段時(shí)(final修飾特殊考慮),比如,使用getstatic或者putstatic指令。(對(duì)應(yīng)訪問變量、賦值變量操作)

④?當(dāng)使用java.lang.reflect包中的方法反射類的方法時(shí)。比如:Class.forName("java.lang.String")

⑤?當(dāng)初始化子類時(shí),如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。

⑥?如果一個(gè)接口定義了default方法,那么直接實(shí)現(xiàn)或者間接實(shí)現(xiàn)該接口的類的初始化,該接口要在其之前被初始化。

⑦?當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類。

⑧?當(dāng)初次調(diào)用MethodHandle實(shí)例時(shí),初始化該MethodHandle指向的方法所在的類。(涉及解析REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄對(duì)應(yīng)的類)

針對(duì)5,補(bǔ)充說明:

? ? 當(dāng)Java虛擬機(jī)初始化一個(gè)類時(shí),要求它的所有父類都已經(jīng)被初始化,但是這條規(guī)則并不適用于接口。

? ? 在初始化一個(gè)類時(shí),并不會(huì)先初始化它所實(shí)現(xiàn)的接口。

? ? 在初始化一個(gè)接口時(shí),并不會(huì)先初始化它的父接口。

? ? 因此,一個(gè)父接口并不會(huì)因?yàn)樗淖咏涌诨蛘邔?shí)現(xiàn)類的初始化而初始化。只有當(dāng)程序首次使用特定接口的靜態(tài)字段時(shí),才會(huì)導(dǎo)致該接口的初始化。

針對(duì)7,說明:

? ? JVM啟動(dòng)的時(shí)候通過引導(dǎo)類加載器加載一個(gè)初始類。這個(gè)類在調(diào)用public?static?void main(String[] args)?方法之前被鏈接和初始化。這個(gè)方法的執(zhí)行將依次導(dǎo)致所需的類的加載,鏈接和初始化。

(2)被動(dòng)使用

? ? 除了以上的情況屬于主動(dòng)使用,其他的情況均屬于被動(dòng)使用。被動(dòng)使用不會(huì)引起類的初始化。

? ? 也就是說:并不是在代碼中出現(xiàn)的類,就一定會(huì)被加載或者初始化。如果不符合主動(dòng)使用的條件,類就不會(huì)初始化。

①?當(dāng)訪問一個(gè)靜態(tài)字段時(shí),只有真正聲明這個(gè)字段的類才會(huì)被初始化。

? ? 當(dāng)通過子類引用父類的靜態(tài)變量,不會(huì)導(dǎo)致子類初始化。(注意,子類會(huì)被加載但是不會(huì)被初始化)

②?通過數(shù)組定義類引用,不會(huì)觸發(fā)此類的初始化。(Parents[] p = new Parent[10];不會(huì)導(dǎo)致Parent類初始化)

③?引用常量不會(huì)觸發(fā)此類或接口的初始化。因?yàn)槌A吭阪溄与A段就已經(jīng)被顯式賦值了。

④?調(diào)用ClassLoader類的loadClass()方法加載一個(gè)類,并不是對(duì)類的主動(dòng)使用,不會(huì)導(dǎo)致類的初始化。

五、過程四:類的Using(使用)

? ? 任何一個(gè)類型在使用之前都必須經(jīng)歷過完整的加載、鏈接和初始化3個(gè)類加載步驟。一旦一個(gè)類型成功經(jīng)歷過這3個(gè)步驟之后,便“萬事俱備只欠東風(fēng)”,就等著開發(fā)者使用了。

? ? 開發(fā)人員可以在程序中訪問和調(diào)用它的靜態(tài)類成員信息(比如:靜態(tài)字段、靜態(tài)方法),或者使用new關(guān)鍵字為其創(chuàng)建對(duì)象實(shí)例。

六、過程五:類的Unloading(卸載)

(1)類、類的加載器、類的實(shí)例之間的引用關(guān)系

? ? 在類加載器的內(nèi)部實(shí)現(xiàn)中,用一個(gè)Java集合來存放所加載類的引用。另一方面,一個(gè)Class對(duì)象總是會(huì)引用它的類加載器,調(diào)用Class對(duì)象的getClassLoader()方法,就能獲得它的類加載器。由此可見,代表某個(gè)類的Class實(shí)例與其類的加載器之間為雙向關(guān)聯(lián)關(guān)系。

? ? 一個(gè)類的實(shí)例總是引用代表這個(gè)類的Class對(duì)象。在Object類中定義了getClass()方法,這個(gè)方法返回代表對(duì)象所屬類的Class對(duì)象的引用。此外,所有的Java類都有一個(gè)靜態(tài)屬性class,它引用代表這個(gè)類的class對(duì)象。

(2)類的聲明周期

? ? 當(dāng)Sample類被加載、鏈接和初始化后,它的生命周期就開始了。當(dāng)代表Sample類的Class對(duì)象不再被引用,即不可觸及時(shí),Class對(duì)象就會(huì)結(jié)束生命周期,Sample類在方法區(qū)內(nèi)的數(shù)據(jù)也會(huì)被卸載,從而結(jié)束Sample類的生命周期。

? ? 一個(gè)類何時(shí)結(jié)束生命周期,取決于代表它的Class對(duì)象何時(shí)結(jié)束生命周期

(3)實(shí)例

?

?? ?loader1變量和obj變量間接應(yīng)用代表Sample類的Class對(duì)象,而objClass變量則直接引用它。

? ? 如果程序運(yùn)行過程中,將上圖左側(cè)三個(gè)引用變量都置為null,此時(shí)Sample對(duì)象結(jié)束生命周期,MyClassLoader對(duì)象結(jié)束生命周期,代表Sample類的Class對(duì)象也結(jié)束生命周期,Sample類在方法區(qū)內(nèi)的二進(jìn)制數(shù)據(jù)被卸載。

? ? 當(dāng)再次有需要時(shí),會(huì)檢查Sample類的Class對(duì)象是否存在,如果存在會(huì)直接使用,不再重新加載;如果不存在Sample類會(huì)被重新加載,在Java虛擬機(jī)的堆區(qū)會(huì)生成一個(gè)新的代表Sample類的Class實(shí)例(可以通過哈希碼看是否是同一個(gè)實(shí)例)

(4)類的卸載

①?啟動(dòng)類加載器加載的類型在整個(gè)運(yùn)行期間是不可能被卸載的(jvm和jls規(guī)范)

②?被系統(tǒng)類加載器和擴(kuò)展類加載器加載的類型在運(yùn)行期間不太可能被卸載,因?yàn)橄到y(tǒng)類加載器實(shí)例或者擴(kuò)展類的實(shí)例基本上在整個(gè)運(yùn)行期間總能直接或間接的訪問得到,其達(dá)到unreachable的可能性極小。

③?被開發(fā)者自定義的類加載器實(shí)例加載的類型只有在很簡單的上下文環(huán)境中才能被卸載,而且一般還要借助于強(qiáng)制調(diào)用虛擬機(jī)的垃圾收集功能才可以做到。可以預(yù)想,稍微復(fù)雜點(diǎn)的應(yīng)用場景中(比如:很多時(shí)候用戶在開發(fā)自定義類加載器實(shí)例的時(shí)候采用緩存的策略以提高系統(tǒng)性能),被加載的類型在運(yùn)行期間也是幾乎不太可能被卸載的(至少卸載的時(shí)間是不確定的)。

? ? 綜合以上三點(diǎn),一個(gè)已經(jīng)加載的類型被卸載的幾率很小至少被卸載的時(shí)間是不確定的。同時(shí)我們可以看得出來,開發(fā)者在開發(fā)代碼的時(shí)候,不應(yīng)該對(duì)虛擬機(jī)的類型卸載做任何假設(shè)的前提下,來實(shí)現(xiàn)系統(tǒng)中的特定功能。

(5)方法區(qū)的垃圾回收

? ? 方法區(qū)的垃圾收集主要回收兩部分內(nèi)容:常量池中廢棄的常量和不再使用的類型。

? ? HotSpot虛擬機(jī)對(duì)常量池的回收策略是很明確的,只要常量池中的常量沒有被任何地方引用,就可以被回收。

? ? 判定一個(gè)常量是否“廢棄”還是相對(duì)簡單,而要判斷一個(gè)類型是否屬于“不再被使用的類”的條件就比較苛刻了。需要同時(shí)滿足下面三個(gè)條件:

? ? ①?該類所有的實(shí)例都已經(jīng)被回收。也就是Java堆中不存在該類及其任何派生子類的實(shí)例。

? ? ②?加載該類的類加載器已經(jīng)被回收。這個(gè)條件除非是經(jīng)過精心設(shè)計(jì)的可替換類加載器的場景,如OSGi、JSP的重加載等,否則通常是很難達(dá)成的。

? ? ③?該類對(duì)應(yīng)的java.lang.Class對(duì)象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

? ? Java虛擬機(jī)被允許對(duì)滿足上述三個(gè)條件的無用類進(jìn)行回收,這里說的僅僅是“被允許”,而并不是和對(duì)象一樣,沒有引用了就必然會(huì)回收。

總結(jié)

以上是生活随笔為你收集整理的java类的完整生命周期详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。