引言
眾所周知,軟件系統有三高:**高并發、高性能、高可用。**三者既有區別也有聯系,門門道道很多,全面討論可以大戰三天三夜。
高并發對于Java開發者來說都不陌生,每年天貓雙十一,秒殺大促等場景阿里都穩穩的扛住了如此大的并發量,因此說,阿里在這方面也有絕對的話語權。
可以從阿里等其他互聯網大廠的招聘要求上看到,有高并發開發經驗優先考慮。因此,Java并發問題一直是各個大廠面試的重點之一。很多程序員每天忙著搬磚,平時接觸不到高并發,哪天受不了跑去面試,還常常會被面試官犀利的高并發問題直接KO。
我們都知道高并發的基礎是并發編程,而阿里新推出的這份《新高并發寶典》層層深入,形成了一個很好的知識系統,讓你在應對面試官的時候完全不慌,所以今天我們就一起來學習一下阿里的高并發架構吧。
類的生命周期
類從被加載到虛擬機內存中開始,到卸載出內存為止,它的整個生命周期包括:加載,驗證,準備,解析,初始化,使用,卸載這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種類加載器互相配合進行加載的,如果有必要,還可以加入自己定義的類加載器。
最后
為什么我不完全主張自學?
①平臺上的大牛基本上都有很多年的工作經驗了,你有沒有想過之前行業的門檻是什么樣的,現在行業門檻是什么樣的?以前企業對于程序員能力要求沒有這么高,甚至十多年前你只要會寫個“Hello World”,你都可以入門這個行業,所以以前要入門是完全可以入門的。
②現在也有一些優秀的年輕大牛,他們或許也是自學成才,但是他們一定是具備優秀的學習能力,優秀的自我管理能力(時間管理,靜心堅持等方面)以及善于發現問題并總結問題。
如果說你認為你的目標十分明確,能做到第②點所說的幾個點,以目前的市場來看,你才真正的適合去自學。
除此之外,對于絕大部分人來說,報班一定是最好的一種快速成長的方式。但是有個問題,現在市場上的培訓機構質量參差不齊,如果你沒有找準一個好的培訓班,完全是浪費精力,時間以及金錢,這個需要自己去甄別選擇。
我個人建議線上比線下的性價比更高,線下培訓價格基本上沒2W是下不來的,線上教育現在比較成熟了,此次疫情期間,學生基本上都感受過線上的學習模式。相比線下而言,線上的優勢以我的了解主要是以下幾個方面:
①價格:線上的價格基本上是線下的一半;
②老師:相對而言線上教育的師資力量比線下更強大也更加豐富,資源更好協調;
③時間:學習時間相對而言更自由,不用裸辭學習,適合邊學邊工作,降低生活壓力;
④課程:從課程內容來說,確實要比線下講的更加深入。
應該學哪些技術才能達到企業的要求?(下圖總結)
Java全套資料免費領取方式:戳這里
應該學哪些技術才能達到企業的要求?(下圖總結)*
Java全套資料免費領取方式:戳這里
[外鏈圖片轉存中…(img-x2tOn3d1-1624444120528)]
[外鏈圖片轉存中…(img-Avamedja-1624444120529)]