原來大廠的Redis分散式鎖都這麼設計的!你值得擁有
類的生命週期
類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入,驗證,準備,解析,初始化,使用,解除安裝這7個階段.其中其中驗證、準備、解析3個部分統稱為連線.
載入、驗證、準備、初始化和解除安裝這五個階段的順序是確定的,型別的載入過程必須按照這種順序按部就班地開始,而解析階段則不一定:它在某些情況下可以在初始化階段之後再開始,這是為了支援Java語言的執行時繫結特性(也稱為動態繫結或晚期繫結)
注意,這裡的幾個階段是按順序開始,而不是按順序進行或完成,因為這些階段通常都是互相交叉地混合進行的,通常在一個階段執行的過程中呼叫或啟用另一個階段。
載入:查詢並載入類的二進位制資料
在載入階段,虛擬機器需要完成以下3件事情:
- 1)通過一個類的全限定名來獲取定義此類的二進位制位元組流。
- 2)將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構。
- 3)在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料的訪問入口。
驗證:確保被載入的類的正確性
驗證是連線階段的第一步,這一階段的目的是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。驗證階段大致會完成4個階段的檢驗動作:
- 檔案格式驗證: 驗證位元組流是否符合Class檔案格式的規範;例如: 是否以
0xCAFEBABE
開頭、主次版本號是否在當前虛擬機器的處理範圍之內、常量池中的常量是否有不被支援的型別。 - 元資料驗證:對位元組碼描述的資訊進行語義分析(注意: 對比
javac
編譯階段的語義分析),以保證其描述的資訊符合Java語言規範的要求;例如: 這個類是否有父類,除了java.lang.Object
之外。 - 位元組碼驗證:通過資料流和控制流分析,確定程式語義是合法的、符合邏輯的。
- 符號引用驗證:確保解析動作能正確執行。
驗證階段是非常重要的,但不是必須的,它對程式執行期沒有影響,如果所引用的類經過反覆驗證,那麼可以考慮採用-Xverifynone
引數來關閉大部分的類驗證措施,以縮短虛擬機器類載入的時間。
準備:為類的靜態變數分配記憶體,並將其初始化為預設值
準備階段是正式為類變數分配記憶體並設定類變數初始值的階段,這些變數所使用的記憶體都將在方法區中進行分配
該階段的注意事項:
- 這時候進行記憶體分配的僅包括類變數(被static修飾的變數),而不包括例項變數,例項變數將會在物件例項化時隨著物件一起分配在Java堆中。
- 這裡所設定的初始值通常情況下是資料型別預設的零值(如
0
、0L
、null
、false
等),而不是被在Java程式碼中被顯式地賦予的值。
比如:假設一個類變數的定義為: public static int value = 3
;那麼變數value在準備階段過後的初始值為0
,而不是3
,因為這時候尚未開始執行任何Java方法,而把value賦值為3的put static
指令是在程式編譯後,存放於類構造器()
方法之中的,所以把value賦值為3的動作將在初始化階段才會執行。
-
對基本資料型別來說,對於類變數(static)和全域性變數,如果不顯式地對其賦值而直接使用,則系統會為其賦予預設的零值,而對於區域性變數來說,在使用前必須顯式地為其賦值,否則編譯時不通過。
-
對於同時被
static
和final
修飾的常量,必須在宣告的時候就為其顯式地賦值,否則編譯時不通過;而只被final修飾的常量則既可以在宣告時顯式地為其賦值,也可以在類初始化時顯式地為其賦值,總之,在使用前必須為其顯式地賦值,系統不會為其賦予預設零值。 -
對於引用資料型別
reference
來說,如陣列引用、物件引用等,如果沒有對其進行顯式地賦值而直接使用,系統都會為其賦予預設的零值,即null
。 -
如果在陣列初始化時沒有對陣列中的各元素賦值,那麼其中的元素將根據對應的資料型別而被賦予預設的零值。
-
如果類欄位的欄位屬性表中存在ConstantValue屬性,即同時被final和static修飾,那麼在準備階段變數value就會被初始化為ConstValue屬性所指定的值。假設上面的類變數value被定義為:
public static final int value = 3;
編譯時Javac將會為value生成ConstantValue屬性,在準備階段虛擬機器就會根據ConstantValue的設定將value賦值為3。我們可以理解為static final
常量在編譯期就將其結果放入了呼叫它的類的常量池中
解析:把類中的符號引用轉換為直接引用
解析階段是虛擬機器將常量池內的符號引用替換為直接引用的過程,解析動作主要針對類
或介面
、欄位
、類方法
、介面方法
、方法型別
、方法控制代碼
和呼叫點
限定符7類符號引用進行。符號引用就是一組符號來描述目標,可以是任何字面量。
直接引用
就是直接指向目標的指標、相對偏移量或一個間接定位到目標的控制代碼。
初始化:對類的靜態變數,靜態程式碼塊執行初始化操作
初始化,為類的靜態變數賦予正確的初始值,JVM負責對類進行初始化,主要對類變數進行初始化。在Java中對類變數進行初始值設定有兩種方式:
- 宣告類變數是指定初始值
- 使用靜態程式碼塊為類變數指定初始值
類初始化的步驟
- 假如這個類還沒有被載入和連線,則程式先載入並連線該類
- 假如該類的直接父類還沒有被初始化,則先初始化其直接父類
- 假如類中有初始化語句,則系統依次執行這些初始化語句
觸發類初始化的時機
只有當對類的主動使用的時候才會導致類的初始化,類的主動使用包括以下六種:
-
使用new關鍵字例項化物件的時候。
-
讀取或設定一個型別的靜態欄位(被final修飾、已在編譯期把結果放入常量池的靜態欄位除外)的時候。
-
呼叫一個型別的靜態方法的時候。
-
使用java.lang.reflect包的方法對型別進行反射呼叫的時候,如果型別沒有進行過初始化,則需要先觸發其初始化。
-
當初始化類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
-
當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含main()方法的那個類),虛擬機器會先初始化這個主類。
以下幾種情況不會執行類初始化
-
通過子類引用父類的靜態欄位,只會觸發父類的初始化,而不會觸發子類的初始化。
-
定義物件陣列,不會觸發該類的初始化。
-
常量在編譯期間會存入呼叫類的常量池中,本質上並沒有直接引用定義常量的類,不會觸 發定義常量所在的類。
-
通過類名獲取 Class 物件,不會觸發類的初始化。
-
通過 Class.forName 載入指定類時,如果指定引數 initialize 為 false 時,也不會觸發類初 始化,其實這個引數是告訴虛擬機器,是否要對類進行初始化。
-
通過 ClassLoader 預設的 loadClass 方法,也不會觸發初始化動作。
使用
類訪問方法區內的資料結構的介面, 物件是Heap區的資料。
解除安裝
Java虛擬機器將結束生命週期的幾種情況
- 執行了System.exit()方法
- 程式正常執行結束
- 程式在執行過程中遇到了異常或錯誤而異常終止
- 由於作業系統出現錯誤而導致Java虛擬機器程序終止
類載入器
什麼是類載入器
虛擬機器設計團隊把類載入階段中的“通過一個類的全限定名來獲取描述此類的二進位制位元組流”這個動作放到Java虛擬機器外部去實現,以便讓應用程式自己決定如何去獲取所需要的類。 實現這個動作的程式碼模組稱為“類載入器”。
類載入器的層次
雙親委派模型要求除了頂層的啟動類載入器外,其餘的類載入器都應有自己的父類載入器。不過這裡類載入器之間的父子關係一般不是以繼承(Inheritance)的關係來實現的,而是通常使用組合(Composition)關係來複用父載入器的程式碼。
從Java虛擬機器的角度來講,只存在兩種不同的類載入器:一種是啟動類載入器(Bootstrap ClassLoader),這個類載入器使用C++語言實現,是虛擬機器自身的一部分;另一種就是所有其他的類載入器,這些類載入器都由Java語言實現,獨立於虛擬機器外部,並且全都繼承自抽象類java.lang.ClassLoader。
從Java開發人員的角度來看,類載入器還可以劃分得更細緻一些,絕大部分Java程式都會使用到以下3種系統提供的類載入器:
啟動類載入器(Bootstrap ClassLoader)
這個類將器負責將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath引數所指定的路徑中的,並且是虛擬機器識別的(按照檔名識別,如rt.jar、tools.jar,名字不符合的類庫即使放在lib目錄中也不會被載入)類庫載入到虛擬機器記憶體中。
擴充套件類載入器(Extension ClassLoader)
這個載入器由sun.misc.Launcher$ExtClassLoader實現,它負責載入<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變數所指定的路徑中的所有類庫,開發者可以直接使用擴充套件類載入器。
應用程式類載入器(Application ClassLoader)
這個類載入器由sun.misc.Launcher$AppClassLoader來實現。由於應用程式類載入器是ClassLoader類中的getSystem-ClassLoader()方法的返回值,所以有些場合中也稱它為“系統類載入器”。
它負責載入使用者類路徑(ClassPath)上所有的類庫,開發者同樣可以直接在程式碼中使用這個類載入器。如果應用程式中沒有自定義過自己的類載入器,一般情況下這個就是程式中預設的類載入器。
我們的應用程式都是由這3種類載入器互相配合進行載入的,如果有必要,還可以加入自己定義的類載入器。
寫在最後
可能有人會問我為什麼願意去花時間幫助大家實現求職夢想,因為我一直堅信時間是可以複製的。我犧牲了自己的大概十個小時寫了這片文章,換來的是成千上萬的求職者節約幾天甚至幾周時間浪費在無用的資源上。
上面的這些(演算法與資料結構)+(Java多執行緒學習手冊)+(計算機網路頂級教程)等學習資源我都在這裡公開分享出來
以上我的經歷希望能夠給大家帶來幫助,需要這些資料的朋友可以戳這裡,就可以免費拿到了