(系列4)JAVA基礎(chǔ)知識(shí)

Java 基礎(chǔ)

語(yǔ)言特性

優(yōu)點(diǎn)

① 平臺(tái)無(wú)關(guān)驱还,擺脫硬件束縛,"一次編寫(xiě)精堕,到處運(yùn)行"寓调。

② 安全的內(nèi)存管理和訪問(wèn)機(jī)制,避免大部分內(nèi)存泄漏和指針越界锄码。

③ 熱點(diǎn)代碼檢測(cè)和運(yùn)行時(shí)編譯優(yōu)化夺英,程序隨運(yùn)行時(shí)長(zhǎng)獲得更高性能。

④ 完善的應(yīng)用程序接口滋捶,支持第三方類(lèi)庫(kù)痛悯。


平臺(tái)無(wú)關(guān)?

JVM: 編譯器生成與計(jì)算機(jī)體系結(jié)構(gòu)無(wú)關(guān)的字節(jié)碼,字節(jié)碼文件不僅能在任何機(jī)器解釋執(zhí)行重窟,還能動(dòng)態(tài)轉(zhuǎn)換成本地機(jī)器碼载萌,轉(zhuǎn)換由 JVM 實(shí)現(xiàn)。JVM 是平臺(tái)相關(guān)的巡扇,屏蔽了不同操作系統(tǒng)的差異扭仁。

語(yǔ)言規(guī)范: 基本數(shù)據(jù)類(lèi)型大小有明確規(guī)定,如 int 永遠(yuǎn) 32 位厅翔,而 C/C++ 可能是 16 位乖坠、32 位,或編譯器開(kāi)發(fā)商指定的其他大小刀闷。數(shù)值類(lèi)型有固定字節(jié)數(shù)熊泵,字符串用標(biāo)準(zhǔn) Unicode 格式。


JDK 和 JRE

JDK: Java Development Kit甸昏,開(kāi)發(fā)工具包顽分。提供了編譯運(yùn)行 Java 程序的各種工具,包括編譯器施蜜、JRE 及常用類(lèi)庫(kù)卒蘸,是 JAVA 核心。

JRE: Java Runtime Environment翻默,運(yùn)行時(shí)環(huán)境缸沃,運(yùn)行 Java 程序的必要環(huán)境,包括 JVM冰蘑、核心類(lèi)庫(kù)和泌、核心配置工具。


值調(diào)用和引用調(diào)用

按值調(diào)用指方法接收調(diào)用者提供的值祠肥,按引用調(diào)用指方法接收調(diào)用者提供的變量地址。

Java 總是按值調(diào)用,方法得到的是參數(shù)的副本仇箱,傳遞對(duì)象時(shí)實(shí)際上傳遞的是對(duì)象引用的副本县恕。

  • 方法不能修改基本數(shù)據(jù)類(lèi)型的參數(shù),例如傳遞了一個(gè) int 值 剂桥,改變 int 值不會(huì)影響實(shí)參忠烛。

  • 方法可以改變對(duì)象參數(shù)的狀態(tài),但不能讓對(duì)象參數(shù)引用新的對(duì)象权逗。例如傳遞了一個(gè) int 數(shù)組美尸,改變數(shù)組內(nèi)容會(huì)影響實(shí)參,而改變其引用并不會(huì)讓實(shí)參引用新的數(shù)組對(duì)象斟薇。


淺拷貝和深拷貝

淺拷貝只復(fù)制當(dāng)前對(duì)象的基本數(shù)據(jù)類(lèi)型及引用變量师坎,沒(méi)有復(fù)制引用變量指向的實(shí)際對(duì)象。修改克隆對(duì)象可能影響原對(duì)象堪滨。

深拷貝完全拷貝基本數(shù)據(jù)類(lèi)型和引用數(shù)據(jù)類(lèi)型胯陋,修改克隆對(duì)象不會(huì)影響原對(duì)象。


反射

在運(yùn)行狀態(tài)中袱箱,對(duì)于任意一個(gè)類(lèi)都能知道它的所有屬性和方法遏乔,對(duì)于任意一個(gè)對(duì)象都能調(diào)用它的任意方法和屬性,這種動(dòng)態(tài)獲取信息及調(diào)用對(duì)象方法的功能稱為反射发笔,缺點(diǎn)是破壞了封裝性及泛型約束盟萨。


Class 類(lèi)

在程序運(yùn)行期間,Java 運(yùn)行時(shí)系統(tǒng)為所有對(duì)象維護(hù)一個(gè)運(yùn)行時(shí)類(lèi)型標(biāo)識(shí)了讨,這個(gè)信息會(huì)跟蹤每個(gè)對(duì)象所屬的類(lèi)鸯旁,虛擬機(jī)利用運(yùn)行時(shí)類(lèi)型信息選擇要執(zhí)行的正確方法,保存這些信息的類(lèi)就是 Class量蕊,這是一個(gè)泛型類(lèi)铺罢。

獲取 Class 對(duì)象:① 類(lèi)名.class 。② 對(duì)象的 getClass方法残炮。③ Class.forName(類(lèi)的全限定名)韭赘。


注解?

注解是一種標(biāo)記,使類(lèi)或接口附加額外信息势就,幫助編譯器和 JVM 完成一些特定功能泉瞻,例如 @Override 標(biāo)識(shí)一個(gè)方法是重寫(xiě)方法。

元注解是自定義注解的注解苞冯,例如:

@Target:約束作用位置袖牙,值是 ElementType 枚舉常量,包括 METHOD 方法舅锄、VARIABLE 變量鞭达、TYPE 類(lèi)/接口、PARAMETER 方法參數(shù)、CONSTRUCTORS 構(gòu)造方法和 LOACL_VARIABLE 局部變量等畴蹭。

@Rentention:約束生命周期坦仍,值是 RetentionPolicy 枚舉常量,包括 SOURCE 源碼叨襟、CLASS 字節(jié)碼和 RUNTIME 運(yùn)行時(shí)繁扎。

@Documented:表明注解應(yīng)該被 javadoc 記錄。


泛型

泛型本質(zhì)是參數(shù)化類(lèi)型糊闽,解決不確定對(duì)象具體類(lèi)型的問(wèn)題梳玫。

泛型的好處:① 類(lèi)型安全,不存在 ClassCastException右犹。② 提升可讀性提澎,編碼階段就顯式知道泛型集合、泛型方法等處理的數(shù)據(jù)類(lèi)型傀履。

泛型用于編譯階段虱朵,編譯后的字節(jié)碼文件不包含泛型類(lèi)型信息,因?yàn)樘摂M機(jī)沒(méi)有泛型類(lèi)型對(duì)象钓账,所有對(duì)象都屬于普通類(lèi)碴犬。例如定義 List<Object>List<String>,在編譯后都會(huì)變成 List 梆暮。


JDK8 新特性

lambda 表達(dá)式:允許把函數(shù)作為參數(shù)傳遞到方法服协,簡(jiǎn)化匿名內(nèi)部類(lèi)代碼。

函數(shù)式接口:使用 @FunctionalInterface 標(biāo)識(shí)啦粹,有且僅有一個(gè)抽象方法偿荷,可被隱式轉(zhuǎn)換為 lambda 表達(dá)式。

方法引用:可以引用已有類(lèi)或?qū)ο蟮姆椒ê蜆?gòu)造方法唠椭,進(jìn)一步簡(jiǎn)化 lambda 表達(dá)式跳纳。

接口:接口可以定義 default 修飾的默認(rèn)方法,降低了接口升級(jí)的復(fù)雜性贪嫂,還可以定義靜態(tài)方法寺庄。

注解:引入重復(fù)注解機(jī)制,相同注解在同地方可以聲明多次力崇。注解作用范圍也進(jìn)行了擴(kuò)展斗塘,可作用于局部變量、泛型亮靴、方法異常等馍盟。

類(lèi)型推測(cè):加強(qiáng)了類(lèi)型推測(cè)機(jī)制场航,使代碼更加簡(jiǎn)潔霍殴。

Optional 類(lèi):處理空指針異常换途,提高代碼可讀性捉兴。

Stream 類(lèi):引入函數(shù)式編程風(fēng)格立膛,提供了很多功能同衣,使代碼更加簡(jiǎn)潔糊饱。方法包括 forEach 遍歷魔市、count 統(tǒng)計(jì)個(gè)數(shù)休讳、filter 按條件過(guò)濾讲婚、limit 取前 n 個(gè)元素、skip 跳過(guò)前 n 個(gè)元素俊柔、map 映射加工筹麸、concat 合并 stream 流等。

日期:增強(qiáng)了日期和時(shí)間 API雏婶,新的 java.time 包主要包含了處理日期物赶、時(shí)間、日期/時(shí)間留晚、時(shí)區(qū)酵紫、時(shí)刻和時(shí)鐘等操作。

JavaScript:提供了一個(gè)新的 JavaScript 引擎错维,允許在 JVM上運(yùn)行特定 JavaScript 應(yīng)用奖地。


異常??

所有異常都是 Throwable 的子類(lèi),分為 Error 和 Exception赋焕。

Error 是 Java 運(yùn)行時(shí)系統(tǒng)的內(nèi)部錯(cuò)誤和資源耗盡錯(cuò)誤参歹,例如 StackOverFlowError 和 OutOfMemoryError,這種異常程序無(wú)法處理隆判。

Exception 分為受檢異常和非受檢異常犬庇,受檢異常要顯式處理,否則編譯出錯(cuò)侨嘀,非受檢異常是運(yùn)行時(shí)異常臭挽,繼承 RuntimeException。

受檢異常:① 無(wú)能為力型咬腕,如字段超長(zhǎng)導(dǎo)致的 SQLException欢峰。② 力所能及型,如未授權(quán)異常 UnAuthorizedException郎汪,程序可跳轉(zhuǎn)權(quán)限申請(qǐng)頁(yè)面赤赊。常見(jiàn)受檢異常還有 FileNotFoundException、ClassNotFoundException煞赢、IOException等抛计。

非受檢異常:① 可預(yù)測(cè)異常,例如 IndexOutOfBoundsException照筑、NullPointerException吹截、ClassCastException 等瘦陈,這類(lèi)異常應(yīng)該提前處理。② 需捕捉異常波俄,例如進(jìn)行 RPC 調(diào)用時(shí)的遠(yuǎn)程服務(wù)超時(shí)晨逝,這類(lèi)異常客戶端必須顯式處理懦铺。③ 可透出異常捉貌,指框架或系統(tǒng)產(chǎn)生的且會(huì)自行處理的異常,例如 Spring 的 NoSuchRequestHandingMethodException冬念,Spring 會(huì)自動(dòng)將異常自動(dòng)映射到合適的狀態(tài)碼趁窃。


數(shù)據(jù)類(lèi)型

基本數(shù)據(jù)類(lèi)型

數(shù)據(jù)類(lèi)型 內(nèi)存大小 默認(rèn)值 取值范圍
byte 1 B (byte)0 -128 ~ 127
short 2 B (short)0 -215 ~ 215-1
int 4 B 0 -231 ~ 231-1
long 8 B 0L -263 ~ 263-1
float 4 B 0.0F ±3.4E+38(有效位數(shù) 6~7 位)
double 8 B 0.0D ±1.7E+308(有效位數(shù) 15 位)
char 英文 1B,中文 UTF-8 占 3B急前,GBK 占 2B醒陆。 '\u0000' '\u0000' ~ '\uFFFF'
boolean 單個(gè)變量 4B / 數(shù)組 1B false true、false

JVM 沒(méi)有 boolean 的字節(jié)碼指令裆针,單個(gè) boolean 變量用 int 代替刨摩,boolean f = false 就是用 ICONST_0 即常數(shù) 0 賦值。boolean 數(shù)組會(huì)編碼成 byte 數(shù)組世吨。

自動(dòng)裝箱是將基本數(shù)據(jù)類(lèi)型包裝為一個(gè)包裝類(lèi)對(duì)象澡刹,例如向一個(gè)泛型為 Integer 的集合添加 int 元素;自動(dòng)拆箱是將一個(gè)包裝類(lèi)對(duì)象轉(zhuǎn)換為基本數(shù)據(jù)類(lèi)型另假,例如將一個(gè) Integer 對(duì)象賦值給一個(gè) int 變量像屋。比較兩個(gè)包裝類(lèi)數(shù)值要用 equals


String?

String 類(lèi)和其存儲(chǔ)數(shù)據(jù)的 value 字節(jié)數(shù)組都是 final 修飾的边篮。對(duì) String 對(duì)象的任何修改實(shí)際都是創(chuàng)建新對(duì)象再引用己莺,并沒(méi)有修改原對(duì)象。

字符串拼接方式

① 直接用 + 戈轿,底層用 StringBuilder 實(shí)現(xiàn)凌受。只適用小數(shù)量,如果在循環(huán)中使用 + 拼接思杯,相當(dāng)于不斷創(chuàng)建新的 StringBuilder 對(duì)象再轉(zhuǎn)換成 String 對(duì)象胜蛉,效率極差。

② 使用 String 的 concat 方法色乾,該方法使用 Arrays.copyOf 創(chuàng)建一個(gè)新的字符數(shù)組 buf 并將當(dāng)前字符串 value 數(shù)組的值拷貝到 buf誊册,之后調(diào)用 getChars 方法用 System.arraycopy 將拼接字符串的值也拷貝到 buf,最后用 buf 作為構(gòu)造參數(shù) new 一個(gè)新的 String 對(duì)象返回暖璧。效率稍高于直接使用 +案怯。

③ 使用 StringBuilder 或 StringBuffer,兩者的 append 方法都繼承自 AbstractStringBuilder澎办,該方法首先使用 Arrays.copyOf 確定新的字符數(shù)組容量嘲碱,再調(diào)用 getChars 方法用 System.arraycopy 將新的值追加到數(shù)組金砍。StringBuilder 是 JDK5 引入的,效率高但線程不安全麦锯,StringBuffer 使用 synchronized 保證線程安全恕稠。


面向?qū)ο?/h3>

面向?qū)ο?/h4>

面向過(guò)程是過(guò)程化思維,代碼松散扶欣,強(qiáng)調(diào)流程化鹅巍,開(kāi)發(fā)時(shí)軟件維護(hù)困難,耦合嚴(yán)重宵蛀;面向?qū)ο蟾m合解決大規(guī)模問(wèn)題昆著,強(qiáng)調(diào)高內(nèi)聚县貌、低耦合术陶,先抽象模型定義共性行為,再解決問(wèn)題煤痕。

封裝是對(duì)象功能內(nèi)聚的表現(xiàn)形式梧宫,在抽象基礎(chǔ)上決定信息是否公開(kāi)及公開(kāi)等級(jí)。主要任務(wù)是對(duì)屬性摆碉、數(shù)據(jù)塘匣、敏感行為實(shí)現(xiàn)隱藏,使對(duì)象關(guān)系變得簡(jiǎn)單巷帝,降低耦合忌卤。

繼承用來(lái)擴(kuò)展類(lèi),子類(lèi)可繼承父類(lèi)的部分屬性和行為楞泼,使模塊具有復(fù)用性驰徊。

多態(tài)以封裝和繼承為基礎(chǔ),根據(jù)運(yùn)行時(shí)對(duì)象實(shí)際類(lèi)型使同一行為具有不同表現(xiàn)形式堕阔。多態(tài)指在編譯層面無(wú)法確定最終調(diào)用的方法體棍厂,在運(yùn)行期由 JVM 動(dòng)態(tài)綁定,調(diào)用合適的重寫(xiě)方法超陆。由于重載屬于靜態(tài)綁定牺弹,本質(zhì)上重載結(jié)果是完全不同的方法,因此多態(tài)一般專指重寫(xiě)时呀。


重載和重寫(xiě)

重載指方法名稱相同张漂,但參數(shù)列表不同,是行為水平方向不同實(shí)現(xiàn)谨娜。對(duì)編譯器來(lái)說(shuō)航攒,方法名稱和參數(shù)列表組成了一個(gè)唯一鍵,稱為方法簽名瞧预,JVM 通過(guò)方法簽名決定調(diào)用哪種重載方法屎债。不管繼承關(guān)系多復(fù)雜仅政,重載在編譯時(shí)可以確定調(diào)用哪個(gè)方法,因此屬于靜態(tài)綁定盆驹。重載順序:① 精確匹配圆丹。② 基本數(shù)據(jù)類(lèi)型自動(dòng)轉(zhuǎn)換成更大表示范圍。③ 自動(dòng)拆箱與裝箱躯喇。④ 子類(lèi)向上轉(zhuǎn)型辫封。⑤ 可變參數(shù)。

重寫(xiě)指子類(lèi)實(shí)現(xiàn)接口或繼承父類(lèi)時(shí)廉丽,保持方法簽名完全相同倦微,實(shí)現(xiàn)不同方法體,是行為垂直方向不同實(shí)現(xiàn)正压。元空間有一個(gè)方法表保存方法信息欣福,如果子類(lèi)重寫(xiě)父類(lèi)的方法,方法表中的方法引用會(huì)指向子類(lèi)焦履。重寫(xiě)方法訪問(wèn)權(quán)限不能變小拓劝,返回類(lèi)型和拋出的異常類(lèi)型不能變大。


Object 類(lèi)?

方法 說(shuō)明
equals 檢測(cè)對(duì)象是否相等嘉裤,默認(rèn)使用 == 比較郑临,可以重寫(xiě)該方法自定義規(guī)則。規(guī)范:自反性屑宠、對(duì)稱性厢洞、傳遞性、一致性典奉、對(duì)于任何非空引用 x躺翻,x.equals(null) 返回 false。
hashCode 每個(gè)對(duì)象都有默認(rèn)散列碼秋柄,值由對(duì)象存儲(chǔ)地址得出获枝。字符串散列碼由內(nèi)容導(dǎo)出,值可能相同骇笔。為了在集合中正確使用省店,一般需要同時(shí)重寫(xiě) equals 和 hashCode,要求 equals 相同 hashCode 必須相同笨触,hashCode 相同 equals 未必相同懦傍。
toString 默認(rèn)打印表示對(duì)象值的一個(gè)字符串。
clone 默認(rèn)聲明為 protected芦劣,只能由本類(lèi)對(duì)象調(diào)用粗俱,且是淺拷貝。一般重寫(xiě) clone 方法需要實(shí)現(xiàn) Cloneable 接口并聲明為 public虚吟,如果沒(méi)有實(shí)現(xiàn) Cloneable 接口會(huì)拋出 CloneNotSupport 異常寸认。
finalize GC 判斷垃圾時(shí)签财,如果對(duì)象沒(méi)有與 GC Roots 相連會(huì)被第一次標(biāo)記,之后判斷對(duì)象是否有必要執(zhí)行 finalize 方法偏塞,有必要?jiǎng)t由一條低調(diào)度優(yōu)先級(jí)的 Finalizer 線程執(zhí)行唱蒸。虛擬機(jī)會(huì)觸發(fā)該方法但不保證結(jié)束,防止方法執(zhí)行緩慢或發(fā)生死循環(huán)灸叼。只要對(duì)象在 finalize 方法中重新與引用鏈相連神汹,就會(huì)在第二次標(biāo)記時(shí)移出回收集合。由于運(yùn)行代價(jià)高且具有不確定性古今,在 JDK9 標(biāo)記為過(guò)時(shí)方法屁魏。
getClass 返回對(duì)象所屬類(lèi)的 Class 對(duì)象。
wait 阻塞持有該對(duì)象鎖的線程捉腥。
notify 喚醒持有該對(duì)象鎖的線程氓拼,notify 隨機(jī)喚醒一個(gè)線程,notifyAll 喚醒全部線程但狭。

內(nèi)部類(lèi)

內(nèi)部類(lèi)可對(duì)同一包中其他類(lèi)隱藏披诗,內(nèi)部類(lèi)方法可以訪問(wèn)定義這個(gè)內(nèi)部類(lèi)的作用域中的數(shù)據(jù),包括 private 數(shù)據(jù)立磁。

內(nèi)部類(lèi)是一個(gè)編譯器現(xiàn)象,與虛擬機(jī)無(wú)關(guān)剥槐。編譯器會(huì)把內(nèi)部類(lèi)轉(zhuǎn)換成常規(guī)的類(lèi)文件唱歧,用 $ 分隔外部類(lèi)名與內(nèi)部類(lèi)名,其中匿名內(nèi)部類(lèi)使用數(shù)字編號(hào)粒竖,虛擬機(jī)對(duì)此一無(wú)所知颅崩。

靜態(tài)內(nèi)部類(lèi): 屬于外部類(lèi),只加載一次蕊苗。作用域僅在包內(nèi)沿后,可通過(guò) 外部類(lèi)名.內(nèi)部類(lèi)名 直接訪問(wèn),只能訪問(wèn)外部類(lèi)所有靜態(tài)屬性和方法朽砰。HashMap 的 Node 節(jié)點(diǎn)尖滚,ReentrantLock 中的 Sync 類(lèi)都是靜態(tài)內(nèi)部類(lèi)。

成員內(nèi)部類(lèi): 屬于外部類(lèi)的每個(gè)對(duì)象瞧柔,隨對(duì)象一起加載漆弄。不可以定義靜態(tài)成員和方法,可訪問(wèn)外部類(lèi)的所有內(nèi)容造锅。

局部?jī)?nèi)部類(lèi): 定義在方法內(nèi)撼唾,不能聲明訪問(wèn)修飾符,只能定義實(shí)例成員變量和實(shí)例方法哥蔚,作用范圍僅在聲明類(lèi)的代碼塊中倒谷。

匿名內(nèi)部類(lèi): 只用一次的沒(méi)有名字的類(lèi)蛛蒙,可以簡(jiǎn)化代碼,創(chuàng)建的對(duì)象類(lèi)型相當(dāng)于 new 的類(lèi)的子類(lèi)類(lèi)型渤愁。用于實(shí)現(xiàn)事件監(jiān)聽(tīng)和其他回調(diào)宇驾。


訪問(wèn)權(quán)限控制符

訪問(wèn)權(quán)限控制符 本類(lèi) 包內(nèi) 包外子類(lèi) 任何地方
public
protected ×
無(wú) × ×
private × × ×

接口和抽象類(lèi)

接口和抽象類(lèi)對(duì)實(shí)體類(lèi)進(jìn)行更高層次的抽象,僅定義公共行為和特征猴伶。

語(yǔ)法維度 抽象類(lèi) 接口
成員變量 無(wú)特殊要求 默認(rèn) public static final 常量
構(gòu)造方法 有構(gòu)造方法课舍,不能實(shí)例化 沒(méi)有構(gòu)造方法,不能實(shí)例化
方法 抽象類(lèi)可以沒(méi)有抽象方法 默認(rèn) public abstract他挎,JDK8 支持默認(rèn)/靜態(tài)方法筝尾,JDK9 支持私有方法。
繼承 單繼承 多繼承

抽象類(lèi)是 is-a 關(guān)系办桨,接口是 can-do 關(guān)系筹淫。與接口相比,抽象類(lèi)通常是對(duì)同類(lèi)事物相對(duì)具體的抽象呢撞。

抽象類(lèi)是模板式設(shè)計(jì)损姜,包含一組具體特征,例如汽車(chē)的底盤(pán)殊霞、控制電路等是抽象出來(lái)的共同特征摧阅,但內(nèi)飾、顯示屏绷蹲、座椅可以根據(jù)不同級(jí)別配置存在不同實(shí)現(xiàn)棒卷。

接口是契約式設(shè)計(jì),是開(kāi)放的祝钢,定義了方法名比规、參數(shù)、返回值拦英、拋出的異常類(lèi)型蜒什,誰(shuí)都可以實(shí)現(xiàn)它,但必須遵守約定。例如所有車(chē)輛都必須實(shí)現(xiàn)剎車(chē)這種強(qiáng)制規(guī)范。

接口是頂級(jí)類(lèi)逃呼,抽象類(lèi)在接口下面的第二層,對(duì)接口進(jìn)行組合岗憋,然后實(shí)現(xiàn)部分接口。當(dāng)糾結(jié)定義接口和抽象類(lèi)時(shí)锚贱,推薦定義為接口仔戈,遵循接口隔離原則,按維度劃分成多個(gè)接口,再利用抽象類(lèi)去實(shí)現(xiàn)监徘,方便擴(kuò)展和重構(gòu)晋修。


集合

ArrayList?

ArrayList 是容量可變列表,使用數(shù)組實(shí)現(xiàn)凰盔,擴(kuò)容時(shí)會(huì)創(chuàng)建更大的數(shù)組墓卦,把原有數(shù)組復(fù)制到新數(shù)組。支持對(duì)元素的隨機(jī)訪問(wèn)户敬,但插入與刪除速度慢落剪。ArrayList 實(shí)現(xiàn)了 RandomAcess 接口,如果類(lèi)實(shí)現(xiàn)了該接口尿庐,使用索引遍歷比迭代器更快忠怖。

elementData 是 ArrayList 的數(shù)據(jù)域,被 transient 修飾抄瑟,序列化時(shí)調(diào)用 writeObject 寫(xiě)入流凡泣,反序列化時(shí)調(diào)用 readObject 重新賦值到新對(duì)象的 elementData。原因是 elementData 容量通常大于實(shí)際存儲(chǔ)元素的數(shù)量皮假,所以只需發(fā)送真正有值的元素鞋拟。

size 是當(dāng)前實(shí)際大小,小于等于 elementData 的大小惹资。

modCount 記錄了 ArrayList 結(jié)構(gòu)性變化的次數(shù)贺纲,繼承自 AbstractList。expectedModCount 是迭代器初始化時(shí)記錄的 modCount 值布轿,每次訪問(wèn)新元素時(shí)都會(huì)檢查 modCount 是否等于 expectedModCount哮笆,不等將拋出異常。這種機(jī)制叫 fail-fast汰扭,所有集合類(lèi)都有。


LinkedList?

LinkedList 本質(zhì)是雙向鏈表福铅,與 ArrayList 相比增刪速度更快萝毛,但隨機(jī)訪問(wèn)慢。除繼承 AbstractList 外還實(shí)現(xiàn)了 Deque 接口滑黔,該接口具有隊(duì)列和棧的性質(zhì)笆包。成員變量被 transient 修飾,原理和 ArrayList 類(lèi)似略荡。

包含三個(gè)重要的成員:size庵佣、first 和 last。size 是雙向鏈表中節(jié)點(diǎn)的個(gè)數(shù)汛兜,first 和 last 分別指向首尾節(jié)點(diǎn)巴粪。

優(yōu)點(diǎn):可以將零散的內(nèi)存單元通過(guò)附加引用的方式關(guān)聯(lián)起來(lái),形成按鏈路順序查找的線性結(jié)構(gòu),內(nèi)存利用率高肛根。


Set

Set 元素不重復(fù)且無(wú)序辫塌,常用實(shí)現(xiàn)有 HashSet、LinkedHashSet 和 TreeSet派哲。

HashSet 通過(guò) HashMap 實(shí)現(xiàn)臼氨,HashMap 的 Key 即 HashSet 存儲(chǔ)的元素,所有 Key 都使用相同的 Value 芭届,一個(gè) Object 類(lèi)型常量储矩。使用 Key 保證元素唯一性,但不保證有序性褂乍。HashSet 判斷元素是否相同時(shí)持隧,對(duì)于包裝類(lèi)型直接按值比較,對(duì)于引用類(lèi)型先比較 hashCode树叽,不同則代表不是同一個(gè)對(duì)象舆蝴,相同則比較 equals,都相同才是同一個(gè)對(duì)象题诵。

LinkedHashSet 繼承自 HashSet洁仗,通過(guò) LinkedHashMap 實(shí)現(xiàn),使用雙向鏈表維護(hù)元素插入順序性锭。

TreeSet 通過(guò) TreeMap 實(shí)現(xiàn)的赠潦,添加元素到集合時(shí)按照比較規(guī)則將其插入合適的位置,保證插入后的集合仍然有序草冈。


TreeMap?

TreeMap 基于紅黑樹(shù)實(shí)現(xiàn)她奥,增刪改查的平均和最差時(shí)間復(fù)雜度均為 O(logn) ,最大特點(diǎn)是 Key 有序怎棱。Key 必須實(shí)現(xiàn) Comparable 接口或 Comparator 接口哩俭,所以 Key 不允許為 null。

TreeMap 依靠 Comparable 或 Comparator 排序拳恋,如果實(shí)現(xiàn)了 Comparator 就會(huì)優(yōu)先使用 compare 方法凡资,否則使用 Comparable 的 compareTo 方法,兩者都不滿足會(huì)拋出異常谬运。

TreeMap 通過(guò) putdeleteEntry 實(shí)現(xiàn)增加和刪除樹(shù)節(jié)點(diǎn)隙赁。插入新節(jié)點(diǎn)的規(guī)則有三個(gè):① 需要調(diào)整的新節(jié)點(diǎn)總是紅色的。② 如果插入新節(jié)點(diǎn)的父節(jié)點(diǎn)是黑色的梆暖,不需要調(diào)整伞访。③ 如果插入新節(jié)點(diǎn)的父節(jié)點(diǎn)是紅色的,由于紅黑樹(shù)不能出現(xiàn)相鄰紅色轰驳,進(jìn)入循環(huán)判斷厚掷,通過(guò)重新著色或左右旋轉(zhuǎn)來(lái)調(diào)整弟灼。


HashMap ?

JDK8 前底層使用數(shù)組加鏈表,JDK8 改為數(shù)組加鏈表/紅黑樹(shù)蝗肪,節(jié)點(diǎn)從 Entry 變?yōu)?Node袜爪。主要成員變量包括 table 數(shù)組、元素?cái)?shù)量 size薛闪、加載因子 loadFactor辛馆。

table 數(shù)組記錄 HashMap 的數(shù)據(jù),每個(gè)下標(biāo)對(duì)應(yīng)一條鏈表豁延,所有哈希沖突的數(shù)據(jù)都會(huì)被存放到同一條鏈表昙篙,Node/Entry 節(jié)點(diǎn)包含四個(gè)成員變量:key、value诱咏、next 和 hash苔可。

數(shù)據(jù)以鍵值對(duì)的形式存在,鍵對(duì)應(yīng)的 hash 值用來(lái)計(jì)算數(shù)組下標(biāo)袋狞,如果兩個(gè)元素 key 的 hash 值一樣焚辅,就會(huì)發(fā)生哈希沖突,被放到同一個(gè)鏈表上苟鸯,為使查詢效率盡可能高同蜻,鍵的 hash 值要盡可能分散。

默認(rèn)初始化容量為 16早处,擴(kuò)容容量必須是 2 的冪次方湾蔓、最大容量為 1<< 30 、默認(rèn)加載因子為 0.75砌梆。


JDK8 之前

hash:計(jì)算元素 key 的散列值

① 處理 String 類(lèi)型時(shí)默责,調(diào)用 stringHash32 方法獲取 hash 值。

② 處理其他類(lèi)型數(shù)據(jù)時(shí)咸包,提供一個(gè)隨機(jī)值 hashSeed 作為計(jì)算初始量桃序,執(zhí)行異或和無(wú)符號(hào)右移使 hash 值更加離散。


indexFor:計(jì)算元素下標(biāo)

將 hash 值和數(shù)組長(zhǎng)度-1 進(jìn)行與操作烂瘫,保證結(jié)果不超過(guò) table 范圍葡缰。


get:獲取元素的 value 值

key 為 null,調(diào)用 getForNullKey 方法:

  • size=0 表示鏈表為空忱反,返回 null。
  • size!=0 說(shuō)明存在鏈表滤愕,遍歷 table[0] 鏈表温算,如果找到了 key=null 的節(jié)點(diǎn)則返回其 value,否則返回 null间影。

key 不為 null注竿,調(diào)用 getEntry 方法:

  • size=0 表示鏈表為空,返回 null 值。
  • size!=0巩割,首先計(jì)算 key 的 hash 值裙顽,然后遍歷該鏈表的所有節(jié)點(diǎn),如果節(jié)點(diǎn)的 key 和 hash 值都和要查找的元素相同則返回其 Entry 節(jié)點(diǎn)宣谈。 如果找到了對(duì)應(yīng)的 Entry 節(jié)點(diǎn)愈犹,調(diào)用 getValue 方法獲取其 value 并返回,否則返回 null闻丑。

put:添加元素

key 為 null漩怎,直接存入 table[0]。

key 不為 null嗦嗡,計(jì)算 key 的 hash 值勋锤,調(diào)用 indexFor 計(jì)算元素下標(biāo) i,遍歷 table[i] 鏈表:

  • key 已存在侥祭,更新 value 然后返回舊 value叁执。
  • key 不存在,將 modCount 加 1矮冬,調(diào)用 addEntry 方法增加一個(gè)節(jié)點(diǎn)并返回 null谈宛。

resize:擴(kuò)容數(shù)組

當(dāng)前容量達(dá)到了最大容量,將閾值設(shè)置為 Integer 最大值欢伏,之后擴(kuò)容不再觸發(fā)入挣。

當(dāng)前容量沒(méi)達(dá)到最大容量,計(jì)算新的容量硝拧,將閾值設(shè)為 newCapacity x loadFactor最大容量 + 1 的較小值径筏。創(chuàng)建一個(gè)容量為 newCapacity 的 Entry 數(shù)組,調(diào)用 transfer 方法將舊數(shù)組的元素轉(zhuǎn)移到新數(shù)組障陶。


transfer:轉(zhuǎn)移元素

遍歷舊數(shù)組的所有元素滋恬,調(diào)用 rehash 方法判斷是否需要哈希重構(gòu),如果需要就重新計(jì)算元素 key 的 hash 值抱究。

調(diào)用 indexFor 方法計(jì)算元素存放的下標(biāo) i恢氯,利用頭插法將舊數(shù)組的元素轉(zhuǎn)移到新數(shù)組。


JDK8

hash:計(jì)算元素 key 的散列值

如果 key 為 null 返回 0鼓寺,否則就將 key 的 hashCode 方法返回值高低16位異或勋拟,讓盡可能多的位參與運(yùn)算,讓結(jié)果的 0 和 1 分布更加均勻妈候,降低哈希沖突概率敢靡。


put:添加元素

調(diào)用 putVal 方法添加元素:

  • 如果 table 為空或不存在元素就進(jìn)行擴(kuò)容,否則計(jì)算元素下標(biāo)位置苦银,不存在就調(diào)用 newNode 創(chuàng)建一個(gè)節(jié)點(diǎn)啸胧。
  • 如果存在元素且是鏈表類(lèi)型赶站,如果首節(jié)點(diǎn)和待插入元素相同,直接更新節(jié)點(diǎn) value纺念。
  • 如果首節(jié)點(diǎn)是 TreeNode 類(lèi)型贝椿,調(diào)用 putTreeVal 方法增加一個(gè)樹(shù)節(jié)點(diǎn),每一次都比較插入節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)的大小陷谱,待插入節(jié)點(diǎn)小就往左子樹(shù)查找烙博,否則往右子樹(shù)查找,找到空位后執(zhí)行兩個(gè)方法:balanceInsert 方法叭首,插入節(jié)點(diǎn)并調(diào)整平衡习勤、moveRootToFront 方法,由于調(diào)整平衡后根節(jié)點(diǎn)可能變化焙格,需要重置根節(jié)點(diǎn)图毕。
  • 如果都不滿足,遍歷鏈表眷唉,根據(jù) hash 和 key 判斷是否重復(fù)予颤,決定更新 value 還是新增節(jié)點(diǎn)。如果遍歷到了鏈表末尾則添加節(jié)點(diǎn)冬阳,如果達(dá)到建樹(shù)閾值 7蛤虐,還需要調(diào)用 treeifyBin 把鏈表重構(gòu)為紅黑樹(shù)。
  • 存放元素后將 modCount 加 1肝陪,如果 ++size > threshold 驳庭,調(diào)用 resize 擴(kuò)容。

get :獲取元素的 value 值

調(diào)用 getNode 方法獲取 Node 節(jié)點(diǎn):

  • 如果數(shù)組不為空且存在元素氯窍,先比較第一個(gè)節(jié)點(diǎn)和要查找元素饲常,如果相同則直接返回。

  • 如果第二個(gè)節(jié)點(diǎn)是 TreeNode 類(lèi)型則調(diào)用 getTreeNode 方法進(jìn)行查找狼讨。

  • 都不滿足贝淤,遍歷鏈表根據(jù) hash 和 key 查找,如果沒(méi)有找到就返回 null政供。

  • 如果節(jié)點(diǎn)不是 null 就返回其 value播聪,否則返回 null。


resize:擴(kuò)容數(shù)組

重新規(guī)劃長(zhǎng)度和閾值布隔,如果長(zhǎng)度發(fā)生了變化离陶,部分?jǐn)?shù)據(jù)節(jié)點(diǎn)也要重新排列。

重新規(guī)劃長(zhǎng)度

① 如果當(dāng)前容量 oldCap > 0 且達(dá)到最大容量衅檀,將閾值設(shè)為 Integer 最大值枕磁,終止擴(kuò)容。

② 如果未達(dá)到最大容量术吝,當(dāng) oldCap << 1 不超過(guò)最大容量就擴(kuò)大為 2 倍计济。

③ 如果都不滿足且當(dāng)前擴(kuò)容閾值 oldThr > 0,使用當(dāng)前擴(kuò)容閾值作為新容量排苍。

④ 否則將新容量置為默認(rèn)初始容量 16沦寂,新擴(kuò)容閾值置為 12。

重新排列數(shù)據(jù)節(jié)點(diǎn)

① 如果節(jié)點(diǎn)為 null 不進(jìn)行處理淘衙。

② 如果節(jié)點(diǎn)不為 null 且沒(méi)有 next 節(jié)點(diǎn)传藏,通過(guò)節(jié)點(diǎn)的 hash 值和 新容量-1 進(jìn)行與運(yùn)算計(jì)算下標(biāo)存入新的 table 數(shù)組。

③ 如果節(jié)點(diǎn)為 TreeNode 類(lèi)型彤守,調(diào)用 split 方法處理毯侦,如果節(jié)點(diǎn)數(shù) hc 達(dá)到 6 會(huì)調(diào)用 untreeify 方法轉(zhuǎn)回鏈表。

④ 如果是鏈表節(jié)點(diǎn)具垫,需要將鏈表拆分為 hash 值超出舊容量的鏈表和未超出容量的鏈表侈离。對(duì)于hash & oldCap == 0 的部分不需要做處理,否則需要放到新的下標(biāo)位置上筝蚕,新下標(biāo) = 舊下標(biāo) + 舊容量卦碾。


線程不安全

JDK7 存在死循環(huán)和數(shù)據(jù)丟失問(wèn)題。

數(shù)據(jù)丟失:

  • 并發(fā)賦值被覆蓋:createEntry 方法中起宽,新添加的元素放在頭部洲胖,使元素可以被更快訪問(wèn),但如果兩個(gè)線程同時(shí)執(zhí)行到此處坯沪,會(huì)導(dǎo)致數(shù)據(jù)覆蓋绿映。

  • 新表被覆蓋: 如果多線程同時(shí) resize ,每個(gè)線程都會(huì) new 一個(gè)數(shù)組腐晾,這是線程內(nèi)的局部對(duì)象叉弦,線程間不可見(jiàn)。遷移完成后resize 的線程會(huì)賦值給 table 線程共享變量赴魁,可能會(huì)覆蓋其他線程的操作卸奉,在新表中插入的對(duì)象都會(huì)被丟棄。

死循環(huán): 擴(kuò)容時(shí) resize 調(diào)用 transfer 使用頭插法遷移元素颖御,雖然 newTable 是局部變量榄棵,但原先 table 中的 Entry 鏈表是共享的,問(wèn)題根源是 Entry 的 next 指針并發(fā)修改潘拱,某線程還沒(méi)有將 table 設(shè)為 newTable 時(shí)用完了 CPU 時(shí)間片疹鳄。

JDK8 在 resize 方法中完成擴(kuò)容,并改用尾插法芦岂,不會(huì)產(chǎn)生死循環(huán)瘪弓,但并發(fā)下仍可能丟失數(shù)據(jù)∏葑睿可用 ConcurrentHashMap 或 Collections.synchronizedMap 包裝同步集合腺怯。


IO 流

BIO

BIO 是同步阻塞式 IO阶祭,JDK1.4 前的 IO 模型屎即。服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接請(qǐng)求對(duì)應(yīng)一個(gè)線程,服務(wù)器需要為每一個(gè)客戶端請(qǐng)求創(chuàng)建一個(gè)線程,如果這個(gè)連接不做任何事會(huì)造成不必要開(kāi)銷(xiāo)粤剧』髂悖可以通過(guò)線程池改善千诬,稱為偽異步 IO仙粱。適用連接數(shù)目少且服務(wù)器資源多的場(chǎng)景。


NIO

NIO 是 JDK1.4 引入的同步非阻塞 IO帜篇。服務(wù)器實(shí)現(xiàn)模式為多個(gè)連接請(qǐng)求對(duì)應(yīng)一個(gè)線程糙捺,客戶端連接請(qǐng)求會(huì)注冊(cè)到一個(gè)多路復(fù)用器 Selector ,Selector 輪詢到連接有 IO 請(qǐng)求時(shí)才啟動(dòng)一個(gè)線程處理笙隙。適用連接數(shù)目多且連接時(shí)間短的場(chǎng)景洪灯。

同步是指線程還是要不斷接收客戶端連接并處理數(shù)據(jù),非阻塞是指如果一個(gè)管道沒(méi)有數(shù)據(jù)逃沿,不需要等待婴渡,可以輪詢下一個(gè)管道。

核心組件:

  • Selector: 多路復(fù)用器凯亮,輪詢檢查多個(gè) Channel 的狀態(tài)边臼,判斷注冊(cè)事件是否發(fā)生,即判斷 Channel 是否處于可讀或可寫(xiě)狀態(tài)假消。使用前需要將 Channel 注冊(cè)到 Selector柠并,注冊(cè)后會(huì)得到一個(gè) SelectionKey,通過(guò) SelectionKey 獲取 Channel 和 Selector 相關(guān)信息富拗。

  • Channel: 雙向通道臼予,替換了 BIO 中的 Stream 流,不能直接訪問(wèn)數(shù)據(jù)啃沪,要通過(guò) Buffer 來(lái)讀寫(xiě)數(shù)據(jù)粘拾,也可以和其他 Channel 交互。

  • Buffer: 緩沖區(qū)创千,是一塊可讀寫(xiě)數(shù)據(jù)的內(nèi)存缰雇。Buffer 三個(gè)重要屬性:position 下次讀寫(xiě)數(shù)據(jù)的位置,limit 本次讀寫(xiě)的極限位置追驴,capacity 最大容量械哟。

    • flip 將寫(xiě)轉(zhuǎn)為讀,底層實(shí)現(xiàn)原理把 position 置 0殿雪,并把 limit 設(shè)為當(dāng)前的 position 值暇咆。
    • clear 將讀轉(zhuǎn)為寫(xiě)模式(用于讀完全部數(shù)據(jù)的情況,把 position 置 0,limit 設(shè)為 capacity)爸业。
    • compact 將讀轉(zhuǎn)為寫(xiě)模式(用于存在未讀數(shù)據(jù)的情況其骄,讓 position 指向未讀數(shù)據(jù)的下一個(gè))。
    • 通道方向和 Buffer 方向相反沃呢,讀數(shù)據(jù)相當(dāng)于向 Buffer 寫(xiě)年栓,寫(xiě)數(shù)據(jù)相當(dāng)于從 Buffer 讀。

    使用步驟:向 Buffer 寫(xiě)數(shù)據(jù)薄霜,調(diào)用 flip 方法轉(zhuǎn)為讀模式,從 Buffer 中讀數(shù)據(jù)纸兔,調(diào)用 clear 或 compact 方法清空緩沖區(qū)惰瓜。


AIO

AIO 是 JDK7 引入的異步非阻塞 IO。服務(wù)器實(shí)現(xiàn)模式為一個(gè)有效請(qǐng)求對(duì)應(yīng)一個(gè)線程汉矿,客戶端的 IO 請(qǐng)求都是由操作系統(tǒng)先完成 IO 操作后再通知服務(wù)器應(yīng)用來(lái)直接使用準(zhǔn)備好的數(shù)據(jù)崎坊。適用連接數(shù)目多且連接時(shí)間長(zhǎng)的場(chǎng)景。

異步是指服務(wù)端線程接收到客戶端管道后就交給底層處理IO通信洲拇,自己可以做其他事情奈揍,非阻塞是指客戶端有數(shù)據(jù)才會(huì)處理,處理好再通知服務(wù)器赋续。

實(shí)現(xiàn)方式包括通過(guò) Future 的 get 方法進(jìn)行阻塞式調(diào)用以及實(shí)現(xiàn) CompletionHandler 接口男翰,重寫(xiě)請(qǐng)求成功的回調(diào)方法 completed 和請(qǐng)求失敗回調(diào)方法 failed


java.io

主要分為字符流和字節(jié)流纽乱,字符流一般用于文本文件蛾绎,字節(jié)流一般用于圖像或其他文件。

字符流包括了字符輸入流 Reader 和字符輸出流 Writer鸦列,字節(jié)流包括了字節(jié)輸入流 InputStream 和字節(jié)輸出流 OutputStream租冠。字符流和字節(jié)流都有對(duì)應(yīng)的緩沖流,字節(jié)流也可以包裝為字符流薯嗤,緩沖流帶有一個(gè) 8KB 的緩沖數(shù)組顽爹,可以提高流的讀寫(xiě)效率。除了緩沖流外還有過(guò)濾流 FilterReader骆姐、字符數(shù)組流 CharArrayReader镜粤、字節(jié)數(shù)組流 ByteArrayInputStream、文件流 FileInputStream 等诲锹。


序列化

Java 對(duì)象在 JVM 退出時(shí)會(huì)全部銷(xiāo)毀繁仁,如果需要將對(duì)象持久化就要通過(guò)序列化實(shí)現(xiàn),將內(nèi)存中的對(duì)象保存在二進(jìn)制流中归园,需要時(shí)再將二進(jìn)制流反序列化為對(duì)象黄虱。對(duì)象序列化保存的是對(duì)象的狀態(tài),因此屬于類(lèi)屬性的靜態(tài)變量不會(huì)被序列化庸诱。

常見(jiàn)的序列化有三種:

  • Java 原生序列化

    實(shí)現(xiàn) Serializabale 標(biāo)記接口捻浦,兼容性最好晤揣,但不支持跨語(yǔ)言,性能一般朱灿。

    序列化和反序列化必須保持序列化 ID 的一致昧识,一般使用 private static final long serialVersionUID 定義序列化 ID,如果不設(shè)置編譯器會(huì)根據(jù)類(lèi)的內(nèi)部實(shí)現(xiàn)自動(dòng)生成該值盗扒。

  • Hessian 序列化

    支持動(dòng)態(tài)類(lèi)型跪楞、跨語(yǔ)言,對(duì)象序列化的二進(jìn)制流可以被其它語(yǔ)言反序列化侣灶。特性:① 自描述序列化類(lèi)型甸祭,不依賴外部描述文件。② 語(yǔ)言無(wú)關(guān)褥影,支持腳本語(yǔ)言池户。③ 協(xié)議簡(jiǎn)單,比 Java 原生序列化高效凡怎。

  • JSON 序列化

    JSON 序列化就是將數(shù)據(jù)對(duì)象轉(zhuǎn)換為 JSON 字符串校焦,在序列化過(guò)程中拋棄了類(lèi)型信息,反序列化時(shí)只有提供類(lèi)型信息才能準(zhǔn)確進(jìn)行统倒。相比前兩種方式可讀性更好寨典,方便調(diào)試。

序列化通常使用網(wǎng)絡(luò)傳輸對(duì)象檐薯,容易遭受攻擊凝赛,Jackson 和 fastjson 都出現(xiàn)過(guò)反序列化漏洞,因此不需要進(jìn)行序列化的敏感屬性應(yīng)加上 transient 關(guān)鍵字坛缕。transient 的作用是把變量生命周期僅限于內(nèi)存墓猎,不會(huì)寫(xiě)到磁盤(pán),變量會(huì)被設(shè)為對(duì)應(yīng)數(shù)據(jù)類(lèi)型的零值赚楚。


JVM

內(nèi)存區(qū)域劃分 ?

程序計(jì)數(shù)器

程序計(jì)數(shù)器是一塊較小的內(nèi)存空間毙沾,可以看作當(dāng)前線程執(zhí)行字節(jié)碼的行號(hào)指示器,是唯一沒(méi)有內(nèi)存溢出的區(qū)域宠页。字節(jié)碼解釋器工作時(shí)通過(guò)改變計(jì)數(shù)器的值選取下一條執(zhí)行指令左胞。分支、循環(huán)举户、跳轉(zhuǎn)烤宙、線程恢復(fù)等功能都需要依賴計(jì)數(shù)器。

如果線程正在執(zhí)行 Java 方法俭嘁,計(jì)數(shù)器記錄正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址躺枕。如果是本地方法,計(jì)數(shù)器值為 Undefined。


Java 虛擬機(jī)棧

Java 虛擬機(jī)棧描述 Java 方法的內(nèi)存模型拐云。當(dāng)有新線程創(chuàng)建時(shí)會(huì)分配一個(gè)棸罩恚空間,棧中元素用于支持虛擬機(jī)進(jìn)行方法調(diào)用叉瘩,每個(gè)方法在執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀存儲(chǔ)方法的局部變量表膳帕、操作棧和方法出口等信息。每個(gè)方法從調(diào)用到執(zhí)行完成薇缅,就是棧幀從入棧到出棧的過(guò)程危彩。

線程請(qǐng)求的棧深度大于虛擬機(jī)允許的深度拋出 StackOverflowError;如果 JVM 棧允許動(dòng)態(tài)擴(kuò)展泳桦,棧擴(kuò)展無(wú)法申請(qǐng)足夠內(nèi)存拋出 OutOfMemoryError(HotSpot 不可動(dòng)態(tài)擴(kuò)展)恬砂。


本地方法棧

本地方法棧與虛擬機(jī)棧作用相似,不同的是虛擬機(jī)棧為 Java 方法服務(wù)蓬痒,本地方法棧為本地方法服務(wù)。虛擬機(jī)規(guī)范對(duì)本地方法棧中方法的語(yǔ)言與數(shù)據(jù)結(jié)構(gòu)無(wú)強(qiáng)制規(guī)定漆羔,例如 HotSpot 將虛擬機(jī)棧和本地方法棧合二為一梧奢。

如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出 StackOverflowError演痒,例如一個(gè)遞歸方法不斷調(diào)用自己亲轨。該異常有明確錯(cuò)誤堆棧可供分析鸟顺,容易定位問(wèn)題惦蚊。

如果 JVM 棧可以動(dòng)態(tài)擴(kuò)展讯嫂,當(dāng)擴(kuò)展無(wú)法申請(qǐng)到足夠內(nèi)存時(shí)會(huì)拋出 OutOfMemoryError蹦锋。HotSpot 不支持虛擬機(jī)棧擴(kuò)展,所以除非在創(chuàng)建線程申請(qǐng)內(nèi)存時(shí)就因無(wú)法獲得足夠內(nèi)存而出現(xiàn) OOM欧芽,否則在線程運(yùn)行時(shí)是不會(huì)因?yàn)閿U(kuò)展而導(dǎo)致溢出的莉掂。


堆是虛擬機(jī)管理的內(nèi)存中最大的一塊,被所有線程共享千扔,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建憎妙。堆用來(lái)存放對(duì)象實(shí)例,Java 里幾乎所有對(duì)象都在堆分配內(nèi)存曲楚。堆可以處于物理上不連續(xù)的內(nèi)存空間厘唾,但對(duì)于數(shù)組這樣的大對(duì)象,多數(shù)虛擬機(jī)出于簡(jiǎn)單高效的考慮會(huì)要求連續(xù)的內(nèi)存空間龙誊。

堆既可以被實(shí)現(xiàn)成固定大小抚垃,也可以是可擴(kuò)展的,通過(guò) -Xms-Xmx 設(shè)置堆的最小和最大容量,主流 JVM 都按照可擴(kuò)展實(shí)現(xiàn)讯柔。

堆用于存儲(chǔ)對(duì)象實(shí)例抡蛙,只要不斷創(chuàng)建對(duì)象并保證 GC Roots 可達(dá)避免垃圾回收,當(dāng)總?cè)萘坑|及最大堆容量后就會(huì)產(chǎn)生 OOM魂迄,例如在 while 死循環(huán)中一直 new 創(chuàng)建實(shí)例粗截。

處理方法:通過(guò)內(nèi)存映像分析工具對(duì) Dump 出的堆轉(zhuǎn)儲(chǔ)快照分析,確認(rèn)導(dǎo)致 OOM 的對(duì)象是否必要捣炬,分清是內(nèi)存泄漏還是內(nèi)存溢出熊昌。

  • 如果是內(nèi)存泄漏,通過(guò)工具查看泄漏對(duì)象到 GC Roots 的引用鏈湿酸,找到泄露對(duì)象是通過(guò)怎樣的引用路徑婿屹、與哪些 GC Roots 關(guān)聯(lián)才導(dǎo)致無(wú)法回收,一般可以準(zhǔn)確定位到產(chǎn)生內(nèi)存泄漏代碼的具體位置推溃。
  • 如果不是內(nèi)存泄漏昂利,即內(nèi)存中對(duì)象都必須存活,應(yīng)當(dāng)檢查 JVM 堆參數(shù)與內(nèi)存相比是否還有調(diào)整空間铁坎。再?gòu)拇a檢查是否存在某些對(duì)象生命周期過(guò)長(zhǎng)蜂奸、存儲(chǔ)結(jié)構(gòu)設(shè)計(jì)不合理等情況,盡量減少程序運(yùn)行期的內(nèi)存消耗硬萍。

方法區(qū)

方法區(qū)用于存儲(chǔ)被虛擬機(jī)加載的類(lèi)型信息扩所、常量、靜態(tài)變量朴乖、即時(shí)編譯器編譯后的代碼緩存等數(shù)據(jù)祖屏。

JDK8 前使用永久代實(shí)現(xiàn)方法區(qū),容易內(nèi)存溢出买羞,因?yàn)橛谰么?-XX:MaxPermSize 上限袁勺,即使不設(shè)置也有默認(rèn)大小。JDK7 把放在永久代的字符串常量池哩都、靜態(tài)變量等移出魁兼,JDK8 時(shí)永久代完全廢棄,改用在本地內(nèi)存中實(shí)現(xiàn)的元空間代替漠嵌,把 JDK7 中永久代剩余內(nèi)容(主要是類(lèi)型信息)全部移到元空間咐汞。

虛擬機(jī)規(guī)范對(duì)方法區(qū)的約束寬松,除和堆一樣不需要連續(xù)內(nèi)存儒鹿、可選擇固定大小化撕、可擴(kuò)展外,還可以不實(shí)現(xiàn)垃圾回收约炎。垃圾回收在方法區(qū)出現(xiàn)較少植阴,主要針對(duì)常量池和類(lèi)型卸載蟹瘾。

方法區(qū)主要存放類(lèi)型信息,只要不斷在運(yùn)行時(shí)產(chǎn)生大量類(lèi)掠手,方法區(qū)就會(huì)溢出憾朴。例如使用反射或 CGLib 在運(yùn)行時(shí)生成大量的類(lèi)。

JDK8 使用元空間取代永久代喷鸽,HotSpot 提供了一些參數(shù)作為元空間防御措施众雷,例如 -XX:MetaspaceSize 指定元空間初始大小,達(dá)到該值會(huì)觸發(fā) GC 進(jìn)行類(lèi)型卸載做祝,同時(shí)收集器會(huì)對(duì)該值進(jìn)行調(diào)整砾省,如果釋放大量空間就適當(dāng)降低該值,如果釋放很少空間就適當(dāng)提高混槐。


運(yùn)行時(shí)常量池

運(yùn)行時(shí)常量池是方法區(qū)的一部分编兄,Class 文件中除了有類(lèi)的版本、字段声登、方法狠鸳、接口等描述信息外,還有一項(xiàng)信息是常量池表悯嗓,用于存放編譯器生成的各種字面量與符號(hào)引用碰煌,這部分內(nèi)容在類(lèi)加載后存放到運(yùn)行時(shí)常量池。一般除了保存 Class 文件中描述的符號(hào)引用外绅作,還會(huì)把符號(hào)引用翻譯的直接引用也存儲(chǔ)在運(yùn)行時(shí)常量池。

運(yùn)行時(shí)常量池相對(duì)于 Class 文件常量池的一個(gè)重要特征是動(dòng)態(tài)性蛾派,Java 不要求常量只有編譯期才能產(chǎn)生俄认,例如 String 的 intern 方法。

intern 方法是一個(gè)本地方法洪乍,作用是如果字符串常量池中已包含一個(gè)等于此 String 對(duì)象的字符串眯杏,則返回池中這個(gè)字符串的 String 對(duì)象的引用,否則將此 String 對(duì)象包含的字符串添加到常量池并返回此 String 對(duì)象的引用壳澳。

在 JDK6 及之前常量池分配在永久代岂贩,因此可以通過(guò) -XX:PermSize-XX:MaxPermSize 限制永久代大小,間接限制常量池巷波。在 while 死循環(huán)中調(diào)用 intern 方法導(dǎo)致運(yùn)行時(shí)常量池溢出萎津。在 JDK7 后不會(huì)出現(xiàn)該問(wèn)題,因?yàn)榇娣旁谟谰么淖址A砍匾呀?jīng)被移至堆中抹镊。


內(nèi)存溢出和內(nèi)存泄漏

內(nèi)存溢出 OutOfMemory锉屈,指程序在申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存空間供其使用垮耳。

內(nèi)存泄露 Memory Leak颈渊,指程序在申請(qǐng)內(nèi)存后遂黍,無(wú)法釋放已申請(qǐng)的內(nèi)存空間,內(nèi)存泄漏最終將導(dǎo)致內(nèi)存溢出俊嗽。


創(chuàng)建對(duì)象

創(chuàng)建對(duì)象的過(guò)程?

① 當(dāng) JVM 遇到字節(jié)碼 new 指令時(shí)雾家,首先檢查能否在常量池中定位到一個(gè)類(lèi)的符號(hào)引用,并檢查該類(lèi)是否已被加載绍豁。

② 在類(lèi)加載檢查通過(guò)后虛擬機(jī)將為新生對(duì)象分配內(nèi)存芯咧。

③ 內(nèi)存分配完成后虛擬機(jī)將成員變量設(shè)為零值,保證對(duì)象的實(shí)例字段可以不賦初值就使用妹田。

④ 設(shè)置對(duì)象頭唬党,包括哈希碼、GC 信息鬼佣、鎖信息驶拱、對(duì)象所屬類(lèi)的類(lèi)元信息等。

⑤ 執(zhí)行 init 方法晶衷,初始化成員變量蓝纲,執(zhí)行實(shí)例化代碼塊,調(diào)用類(lèi)的構(gòu)造方法晌纫,并把堆內(nèi)對(duì)象的首地址賦值給引用變量税迷。


分配內(nèi)存

分配內(nèi)存相當(dāng)于把一塊確定大小的內(nèi)存塊從 Java 堆劃分出來(lái)倦零。

指針碰撞: 假設(shè) Java 堆內(nèi)存規(guī)整十嘿,利用一個(gè)指針將內(nèi)存分為兩部分,分配內(nèi)存就是把指針向空閑方向挪動(dòng)一段與對(duì)象大小相等的距離偶宫。

空間列表: 如果 Java 堆內(nèi)存不規(guī)整哥牍,虛擬機(jī)維護(hù)一個(gè)列表記錄可用內(nèi)存毕泌,分配時(shí)從列表中找到一塊足夠的空間劃分給對(duì)象并更新列表。

選擇哪種分配方式由堆是否規(guī)整決定嗅辣,堆是否規(guī)整由垃圾收集器是否有空間壓縮能力決定撼泛。使用 Serial、ParNew 等收集器時(shí)澡谭,系統(tǒng)采用指針碰撞愿题;使用 CMS 時(shí),采用空間列表蛙奖。

修改指針位置是線程不安全的潘酗,存在正給對(duì)象分配內(nèi)存,指針還沒(méi)來(lái)得及修改雁仲,其它對(duì)象又使用指針?lè)峙鋬?nèi)存的情況崎脉。解決方法:① CAS 加失敗重試。② 把內(nèi)存分配按線程劃分在不同空間伯顶,叫做本地線程分配緩沖 TLAB囚灼,哪個(gè)線程要分配內(nèi)存就在對(duì)應(yīng)的 TLAB 分配骆膝。


內(nèi)存布局

對(duì)象頭

占 12B,包括對(duì)象標(biāo)記和類(lèi)型指針灶体。對(duì)象標(biāo)記存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)阅签,如哈希碼、GC 分代年齡蝎抽、鎖標(biāo)志政钟、偏向線程 ID 等,這部分占 8B樟结,稱為 Mark Word养交。Mark Word 被設(shè)計(jì)為動(dòng)態(tài)數(shù)據(jù)結(jié)構(gòu),以便在極小的空間存儲(chǔ)更多數(shù)據(jù)瓢宦,根據(jù)對(duì)象狀態(tài)復(fù)用存儲(chǔ)空間碎连。類(lèi)型指針是對(duì)象指向它的類(lèi)型元數(shù)據(jù)的指針,占 4B驮履,JVM 通過(guò)該指針來(lái)確定對(duì)象是哪個(gè)類(lèi)的實(shí)例鱼辙。

實(shí)例數(shù)據(jù)

對(duì)象真正存儲(chǔ)的有效信息,即本類(lèi)對(duì)象的實(shí)例成員變量和所有可見(jiàn)的父類(lèi)成員變量玫镐。存儲(chǔ)順序會(huì)受到虛擬機(jī)分配策略參數(shù)和字段在源碼中定義順序的影響倒戏。

對(duì)齊填充

僅起占位符作用。虛擬機(jī)的內(nèi)存管理系統(tǒng)要求任何對(duì)象的大小必須是 8B 的倍數(shù)恐似,如果沒(méi)有對(duì)齊需要對(duì)齊填充補(bǔ)全杜跷。


垃圾回收

判斷垃圾

引用計(jì)數(shù)

在對(duì)象中添加一個(gè)引用計(jì)數(shù)器,如果被引用計(jì)數(shù)器加 1矫夷,引用失效時(shí)計(jì)數(shù)器減 1葱椭,如果計(jì)數(shù)器為 0 則被標(biāo)記為垃圾。簡(jiǎn)單高效口四,但在 Java 中很少使用,因?yàn)榇嬖趯?duì)象循環(huán)引用的問(wèn)題秦陋,導(dǎo)致計(jì)數(shù)器無(wú)法清零蔓彩。

可達(dá)性分析

通過(guò)一系列稱為 GC Roots 的根對(duì)象作為起始節(jié)點(diǎn)集,從這些節(jié)點(diǎn)開(kāi)始根據(jù)引用關(guān)系向下搜索驳概,搜索過(guò)程走過(guò)的路徑稱為引用鏈赤嚼,如果某個(gè)對(duì)象到 GC Roots 沒(méi)有任何引用鏈相連則會(huì)被標(biāo)記為垃圾∷秤郑可作為 GC Roots 的對(duì)象包括:虛擬機(jī)棧和本地方法棧中引用的對(duì)象更卒、類(lèi)靜態(tài)屬性引用的對(duì)象、常量引用的對(duì)象稚照。


引用類(lèi)型

JDK1.2 后對(duì)引用進(jìn)行了擴(kuò)充蹂空,按強(qiáng)度分為四種俯萌。

強(qiáng)引用: 最常見(jiàn)的引用,例如使用 new 創(chuàng)建對(duì)象上枕。只要對(duì)象有強(qiáng)引用指向且 GC Roots 可達(dá)咐熙,即使瀕臨內(nèi)存耗盡也不會(huì)回收。

軟引用: 弱于強(qiáng)引用辨萍,描述非必需對(duì)象棋恼。系統(tǒng)發(fā)生內(nèi)存溢出前,會(huì)把軟引用關(guān)聯(lián)的對(duì)象加入回收范圍锈玉。

弱引用: 弱于軟引用爪飘,描述非必需對(duì)象。弱引用關(guān)聯(lián)的對(duì)象只能生存到下次 YGC 前拉背,GC 時(shí)無(wú)論內(nèi)存是否足夠都會(huì)回收师崎。

虛引用: 最弱的引用,無(wú)法通過(guò)引用獲取對(duì)象去团。唯一目的是在對(duì)象被回收時(shí)收到一個(gè)系統(tǒng)通知抡诞,必須與引用隊(duì)列配合。


GC 算法

標(biāo)記清除

分為標(biāo)記和清除階段土陪,首先從每個(gè) GC Roots 出發(fā)依次標(biāo)記有引用關(guān)系的對(duì)象昼汗,最后清除沒(méi)有標(biāo)記的對(duì)象。如果堆包含大量對(duì)象且大部分需要回收鬼雀,必須進(jìn)行大量標(biāo)記清除顷窒,效率低。

缺點(diǎn):存在內(nèi)存空間碎片化問(wèn)題源哩,分配大對(duì)象時(shí)容易觸發(fā) Full GC鞋吉。

標(biāo)記復(fù)制

為解決內(nèi)存碎片,將可用內(nèi)存按容量劃分為大小相等的兩塊励烦,每次只使用其中一塊谓着,主要用于新生代。

缺點(diǎn):對(duì)象存活率高時(shí)要進(jìn)行較多復(fù)制操作坛掠,效率低赊锚。如果不想浪費(fèi)空間就需要有額外空間分配擔(dān)保,老年代一般不使用此算法屉栓。

HotSpot 把新生代劃分為一塊較大的 Eden 和兩塊較小的 Survivor舷蒲,每次分配內(nèi)存只使用 Eden 和其中一塊 Survivor。垃圾收集時(shí)將 Eden 和 Survivor 中仍然存活的對(duì)象一次性復(fù)制到另一塊 Survivor 上友多,然后直接清理掉 Eden 和已用過(guò)的那塊 Survivor牲平。HotSpot 默認(rèn)Eden 和 Survivor 的大小比例是 8:1,每次新生代中可用空間為整個(gè)新生代的 90%域滥。

標(biāo)記整理

老年代使用標(biāo)記整理算法纵柿,標(biāo)記過(guò)程與標(biāo)記清除算法一樣蜈抓,但不直接清理可回收對(duì)象,而是讓所有存活對(duì)象都向內(nèi)存空間一端移動(dòng)藐窄,然后清理掉邊界以外的內(nèi)存资昧。

標(biāo)記清除與標(biāo)記整理的區(qū)別:前者是一種非移動(dòng)式算法,后者是移動(dòng)式的荆忍。如果移動(dòng)存活對(duì)象格带,尤其是在老年代這種每次回收都有大量對(duì)象存活的區(qū)域,開(kāi)銷(xiāo)很大刹枉,而且移動(dòng)必須暫停用戶線程叽唱;如果不移動(dòng)會(huì)導(dǎo)致空間碎片問(wèn)題。


垃圾收集器

Serial

最基礎(chǔ)的收集器微宝,使用復(fù)制算法棺亭、單線程工作,進(jìn)行垃圾收集時(shí)必須暫停其他線程蟋软。

Serial 是客戶端模式的默認(rèn)新生代收集器镶摘,對(duì)于處理器核心較少的環(huán)境,由于沒(méi)有線程開(kāi)銷(xiāo)岳守,可獲得最高的單線程收集效率凄敢。


ParNew

Serial 的多線程版本,ParNew 是虛擬機(jī)在服務(wù)端模式的默認(rèn)新生代收集器湿痢,一個(gè)重要原因是除了 Serial 外只有它能與 CMS 配合涝缝。從 JDK9 開(kāi)始,ParNew 加 CMS 不再是官方推薦的解決方案譬重,官方希望它被 G1 取代拒逮。


Parallel Scavenge

基于復(fù)制算法、多線程工作的新生代收集器臀规。

它的目標(biāo)是高吞吐量滩援,吞吐量就是處理器用于運(yùn)行用戶代碼的時(shí)間與處理器消耗總時(shí)間的比值。


Serial Old

Serial 的老年代版本塔嬉,使用整理算法玩徊。

Serial Old 是客戶端模式的默認(rèn)老年代收集器,用于服務(wù)端有兩種用途:① JDK5 前與 Parallel Scavenge 搭配邑遏。② 作為 CMS 失敗預(yù)案。


Parellel Old

Parallel Scavenge 的老年代版本恰矩,支持多線程记盒,基于整理算法。JDK6 提供外傅,注重吞吐量可考慮 Parallel Scavenge 加 Parallel Old 組合纪吮。


CMS

以獲取最短回收停頓時(shí)間為目標(biāo)俩檬,基于清除算法,過(guò)程分為四個(gè)步驟:

  • 初始標(biāo)記:標(biāo)記 GC Roots 能直接關(guān)聯(lián)的對(duì)象碾盟,速度很快棚辽。

  • 并發(fā)標(biāo)記:從 GC Roots 的直接關(guān)聯(lián)對(duì)象開(kāi)始遍歷整個(gè)對(duì)象圖,耗時(shí)較長(zhǎng)但不需要停頓用戶線程冰肴。

  • 重新標(biāo)記:修正并發(fā)標(biāo)記期間因用戶程序運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的記錄屈藐。

  • 并發(fā)清除:清理標(biāo)記階段判斷的已死亡對(duì)象,不需要移動(dòng)存活對(duì)象熙尉,該階段也可與用戶線程并發(fā)联逻。

缺點(diǎn):① 對(duì)處理器資源敏感,并發(fā)階段雖然不會(huì)導(dǎo)致用戶線程暫停检痰,但會(huì)降低吞吐量包归。② 無(wú)法處理浮動(dòng)垃圾,有可能出現(xiàn)并發(fā)失敗而導(dǎo)致 Full GC铅歼。③ 基于清除算法公壤,會(huì)產(chǎn)生空間碎片。


G1

開(kāi)創(chuàng)了面向局部收集的設(shè)計(jì)思路和基于 Region 的內(nèi)存布局椎椰,主要面向服務(wù)端厦幅,最初設(shè)計(jì)目標(biāo)是替換 CMS。

G1 可面向堆任何部分來(lái)組成回收集進(jìn)行回收俭识,衡量標(biāo)準(zhǔn)不再是分代慨削,而是哪塊內(nèi)存中垃圾的價(jià)值最大。價(jià)值即回收所獲空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值套媚,G1 在后臺(tái)維護(hù)一個(gè)優(yōu)先級(jí)列表缚态,每次根據(jù)用戶設(shè)定允許的收集停頓時(shí)間優(yōu)先處理回收價(jià)值最大的 Region。

G1 運(yùn)作過(guò)程:

  • 初始標(biāo)記:標(biāo)記 GC Roots 能直接關(guān)聯(lián)到的對(duì)象堤瘤,讓下一階段用戶線程并發(fā)運(yùn)行時(shí)能正確地在可用 Region 中分配新對(duì)象玫芦。需要 STW 但耗時(shí)很短,在 Minor GC 時(shí)同步完成本辐。
  • 并發(fā)標(biāo)記:從 GC Roots 開(kāi)始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析桥帆,遞歸掃描整個(gè)堆的對(duì)象圖。耗時(shí)長(zhǎng)但可與用戶線程并發(fā)慎皱,掃描完成后要重新處理 SATB 記錄的在并發(fā)時(shí)有變動(dòng)的對(duì)象老虫。
  • 最終標(biāo)記:對(duì)用戶線程做短暫暫停,處理并發(fā)階段結(jié)束后仍遺留下來(lái)的少量 SATB 記錄茫多。
  • 篩選回收:對(duì)各 Region 的回收價(jià)值排序祈匙,根據(jù)用戶期望停頓時(shí)間制定回收計(jì)劃。必須暫停用戶線程,由多條收集線程并行完成夺欲。

ZGC

JDK11 中加入的具有實(shí)驗(yàn)性質(zhì)的低延遲垃圾收集器跪帝,目標(biāo)是盡可能在不影響吞吐量的前提下,實(shí)現(xiàn)在任意堆內(nèi)存大小都可以把停頓時(shí)間限制在 10ms 以內(nèi)的低延遲些阅。

基于 Region 內(nèi)存布局伞剑,不設(shè)分代,使用了讀屏障市埋、染色指針和內(nèi)存多重映射等技術(shù)實(shí)現(xiàn)可并發(fā)的標(biāo)記整理黎泣。ZGC 的 Region 具有動(dòng)態(tài)性,是動(dòng)態(tài)創(chuàng)建和銷(xiāo)毀的腰素,并且容量大小也是動(dòng)態(tài)變化的聘裁。


內(nèi)存分配與回收策略

對(duì)象優(yōu)先在 Eden 區(qū)分配

大多數(shù)情況下對(duì)象在新生代 Eden 區(qū)分配,當(dāng) Eden 沒(méi)有足夠空間時(shí)將發(fā)起一次 Minor GC弓千。

大對(duì)象直接進(jìn)入老年代

大對(duì)象指需要大量連續(xù)內(nèi)存空間的對(duì)象衡便,例如很長(zhǎng)的字符串或大數(shù)組,容易導(dǎo)致內(nèi)存還有不少空間就提前觸發(fā) GC 以獲得足夠連續(xù)空間洋访。

HotSpot 提供了 -XX:PretenureSizeThreshold 參數(shù)镣陕,大于該值的對(duì)象直接在老年代分配,避免在 Eden 和 Survivor 間來(lái)回復(fù)制姻政。

長(zhǎng)期存活對(duì)象進(jìn)入老年代

虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡計(jì)數(shù)器呆抑,存儲(chǔ)在對(duì)象頭。如果經(jīng)歷過(guò)第一次 Minor GC 仍然存活且能被 Survivor 容納汁展,該對(duì)象就會(huì)被移動(dòng)到 Survivor 中并將年齡設(shè)置為 1鹊碍。對(duì)象在 Survivor 中每熬過(guò)一次 Minor GC 年齡就加 1 ,當(dāng)增加到一定程度(默認(rèn)15)就會(huì)被晉升到老年代食绿。對(duì)象晉升老年代的閾值可通過(guò) -XX:MaxTenuringThreshold 設(shè)置侈咕。

動(dòng)態(tài)對(duì)象年齡判定

為了適應(yīng)不同內(nèi)存狀況,虛擬機(jī)不要求對(duì)象年齡達(dá)到閾值才能晉升老年代器紧,如果在 Survivor 中相同年齡所有對(duì)象大小的總和大于 Survivor 的一半耀销,年齡不小于該年齡的對(duì)象就可以直接進(jìn)入老年代。

空間分配擔(dān)保

MinorGC 前虛擬機(jī)必須檢查老年代最大可用連續(xù)空間是否大于新生代對(duì)象總空間铲汪,如果滿足則說(shuō)明這次 Minor GC 確定安全熊尉。

如果不滿足,虛擬機(jī)會(huì)查看 -XX:HandlePromotionFailure 參數(shù)是否允許擔(dān)保失敗掌腰,如果允許會(huì)繼續(xù)檢查老年代最大可用連續(xù)空間是否大于歷次晉升老年代對(duì)象的平均大小狰住,如果滿足將冒險(xiǎn)嘗試一次 Minor GC,否則改成一次 FullGC齿梁。

冒險(xiǎn)是因?yàn)樾律褂脧?fù)制算法催植,只用一個(gè) Survivor,大量對(duì)象在 Minor GC 后仍然存活時(shí)需要老年代接收 Survivor 無(wú)法容納的對(duì)象。


故障處理工具

jps:虛擬機(jī)進(jìn)程狀況工具

列出正在運(yùn)行的虛擬機(jī)進(jìn)程查邢,使用 Windows 的任務(wù)管理器或 UNIX 的 ps 命令也可以查詢,但如果同時(shí)啟動(dòng)多個(gè)進(jìn)程酵幕,必須依賴 jps扰藕。

jstat:虛擬機(jī)統(tǒng)計(jì)信息監(jiān)視工具

監(jiān)視虛擬機(jī)各種運(yùn)行狀態(tài)信息,顯示本地或遠(yuǎn)程虛擬機(jī)進(jìn)程中的類(lèi)加載芳撒、內(nèi)存邓深、垃圾收集、即時(shí)編譯器等運(yùn)行時(shí)數(shù)據(jù)笔刹。

jinfo:Java 配置信息工具

查看虛擬機(jī)各項(xiàng)參數(shù)芥备,使用 jps -v 可查看虛擬機(jī)啟動(dòng)時(shí)顯式指定的參數(shù),但如果想知道未顯式指定的參數(shù)只能使用 jinfo -flag舌菜。

jmap:Java 內(nèi)存映像工具

用于生成堆轉(zhuǎn)儲(chǔ)快照萌壳,還可以查詢 finalize 執(zhí)行隊(duì)列、Java 堆和方法區(qū)的詳細(xì)信息日月,如空間使用率袱瓮,當(dāng)前使用的是哪種收集器等。

jhat:虛擬機(jī)堆轉(zhuǎn)儲(chǔ)快照分析工具

JDK 提供 jhat 與 jmap 搭配使用分析堆轉(zhuǎn)儲(chǔ)快照爱咬。jhat 內(nèi)置微型的 HTTP/Web 服務(wù)器尺借,堆轉(zhuǎn)儲(chǔ)快照的分析結(jié)果后可以在瀏覽器查看。

jstack:Java 堆棧跟蹤工具

用于生成虛擬機(jī)當(dāng)前時(shí)刻的線程快照精拟,定位線程出現(xiàn)長(zhǎng)時(shí)間停頓的原因燎斩,如線程間死鎖、死循環(huán)蜂绎、請(qǐng)求外部資源導(dǎo)致的長(zhǎng)時(shí)間掛起等栅表。


類(lèi)加載機(jī)制

Java 程序運(yùn)行過(guò)程

首先通過(guò) Javac 編譯器將 .java 文件轉(zhuǎn)為 JVM 可加載的 .class 字節(jié)碼文件。編譯過(guò)程分為: ① 詞法解析荡碾,通過(guò)空格分割出單詞谨读、操作符、控制符等信息坛吁,形成 token 信息流劳殖,傳遞給語(yǔ)法解析器。② 語(yǔ)法解析拨脉,把 token 信息流按照 Java 語(yǔ)法規(guī)則組裝成語(yǔ)法樹(shù)哆姻。③ 語(yǔ)義分析,檢查關(guān)鍵字使用是否合理玫膀、類(lèi)型是否匹配矛缨、作用域是否正確等。④ 字節(jié)碼生成,將前面各個(gè)步驟的信息轉(zhuǎn)換為字節(jié)碼箕昭。

之后通過(guò)即時(shí)編譯器 JIT 把字節(jié)碼文件編譯成本地機(jī)器碼灵妨。Java 程序最初都是通過(guò)解釋器進(jìn)行解釋執(zhí)行的,當(dāng)虛擬機(jī)發(fā)現(xiàn)某個(gè)方法或代碼塊的運(yùn)行特別頻繁落竹,就會(huì)認(rèn)定其為熱點(diǎn)代碼泌霍,熱點(diǎn)代碼的檢測(cè)主要有采樣和計(jì)數(shù)器兩種方式,為了提高熱點(diǎn)代碼的執(zhí)行效率述召,虛擬機(jī)會(huì)把它們編譯成本地機(jī)器碼朱转。


類(lèi)加載

Class 文件中的信息需要加載到虛擬機(jī)后才能使用。JVM 把描述類(lèi)的數(shù)據(jù)從 Class 文件加載到內(nèi)存积暖,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)藤为、解析和初始化兔辅,最終形成可以被虛擬機(jī)直接使用的 Java 類(lèi)型逊躁,這個(gè)過(guò)程稱為虛擬機(jī)的類(lèi)加載機(jī)制。

Java 類(lèi)型的加載淮菠、連接和初始化都是在運(yùn)行期間完成的遍愿,這增加了性能開(kāi)銷(xiāo)窿吩,但提供高擴(kuò)展性,Java 動(dòng)態(tài)擴(kuò)展的特性就是依賴運(yùn)行期動(dòng)態(tài)加載和連接實(shí)現(xiàn)的错览。

類(lèi)型從被加載到卸出纫雁,整個(gè)生命周期經(jīng)歷加載、驗(yàn)證倾哺、準(zhǔn)備轧邪、解析、初始化羞海、使用和卸載七個(gè)階段忌愚,其中驗(yàn)證、解析和初始化三部分稱為連接却邓。加載硕糊、驗(yàn)證、準(zhǔn)備腊徙、初始化的順序是確定的简十,解析則不一定:可能在初始化后再開(kāi)始,這是為了支持 Java 的動(dòng)態(tài)綁定撬腾。


類(lèi)初始化的情況

① 遇到 new螟蝙、getstaticputstatic 字節(jié)碼指令時(shí)民傻,還未初始化胰默。例如 new 實(shí)例化對(duì)象场斑、設(shè)置靜態(tài)字段、調(diào)用靜態(tài)方法牵署。

② 對(duì)類(lèi)反射調(diào)用時(shí)漏隐,還未初始化。

③ 初始化類(lèi)時(shí)奴迅,父類(lèi)還未初始化锁保。(接口初始化時(shí)不要求父接口初始化,只有在真正使用父接口時(shí)才會(huì)初始化半沽,如引用接口常量)

④ 虛擬機(jī)啟動(dòng)時(shí),會(huì)先初始化包含 main 方法的主類(lèi)吴菠。

⑤ 接口定義了默認(rèn)方法者填,如果接口的實(shí)現(xiàn)類(lèi)初始化,接口要在其之前初始化做葵。

其余所有引用類(lèi)型的方式都不會(huì)觸發(fā)初始化占哟,稱為被動(dòng)引用。被動(dòng)引用舉例:① 子類(lèi)使用父類(lèi)的靜態(tài)字段時(shí)酿矢,只有父類(lèi)被初始化榨乎。② 通過(guò)數(shù)組定義使用類(lèi)。③ 常量在編譯期會(huì)存入調(diào)用類(lèi)的常量池瘫筐,不會(huì)初始化定義常量的類(lèi)蜜暑。


類(lèi)加載的過(guò)程?

加載

通過(guò)一個(gè)類(lèi)的全限定類(lèi)名獲取對(duì)應(yīng)的二進(jìn)制字節(jié)流,將流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)區(qū)策肝,然后在內(nèi)存中生成對(duì)應(yīng)該類(lèi)的 Class 實(shí)例肛捍,作為方法區(qū)中這個(gè)類(lèi)的數(shù)據(jù)訪問(wèn)入口。

驗(yàn)證

確保 Class 文件的字節(jié)流符合約束之众。如果虛擬機(jī)不檢查輸入的字節(jié)流拙毫,可能因?yàn)檩d入有錯(cuò)誤或惡意企圖的字節(jié)流而導(dǎo)致系統(tǒng)受攻擊。驗(yàn)證主要包含:文件格式驗(yàn)證棺禾、元數(shù)據(jù)驗(yàn)證缀蹄、字節(jié)碼驗(yàn)證、符號(hào)引用驗(yàn)證膘婶。

驗(yàn)證通過(guò)后對(duì)程序運(yùn)行期沒(méi)有任何影響缺前。如果代碼已被反復(fù)使用和驗(yàn)證過(guò),在生產(chǎn)環(huán)境就可以考慮關(guān)閉大部分驗(yàn)證縮短類(lèi)加載時(shí)間悬襟。

準(zhǔn)備

為類(lèi)靜態(tài)變量分配內(nèi)存并設(shè)置零值诡延,該階段進(jìn)行的內(nèi)存分配僅包括類(lèi)變量,不包括實(shí)例變量古胆。如果變量被 final 修飾肆良,編譯時(shí) Javac 會(huì)為變量生成 ConstantValue 屬性筛璧,準(zhǔn)備階段虛擬機(jī)會(huì)將變量值設(shè)為代碼值。

解析

將常量池內(nèi)的符號(hào)引用替換為直接引用惹恃。

符號(hào)引用以一組符號(hào)描述引用目標(biāo)夭谤,可以是任何形式的字面量,只要使用時(shí)能無(wú)歧義地定位目標(biāo)即可巫糙,引用目標(biāo)不一定已經(jīng)加載到虛擬機(jī)內(nèi)存朗儒;直接引用是可以直接指向目標(biāo)的指針、相對(duì)偏移量或能間接定位到目標(biāo)的句柄参淹,引用目標(biāo)必須已在虛擬機(jī)的內(nèi)存中存在醉锄。

初始化

直到該階段 JVM 才開(kāi)始執(zhí)行類(lèi)中編寫(xiě)的代碼。準(zhǔn)備階段時(shí)變量賦過(guò)零值浙值,初始化階段會(huì)根據(jù)程序員的編碼去初始化類(lèi)變量和其他資源恳不。初始化階段就是執(zhí)行類(lèi)構(gòu)造方法中的 <client> 方法,該方法是 Javac 自動(dòng)生成的开呐。


類(lèi)加載器

啟動(dòng)類(lèi)加載器

在 JVM 啟動(dòng)時(shí)創(chuàng)建烟勋,負(fù)責(zé)加載最核心的類(lèi),例如 Object筐付、System 等卵惦。無(wú)法被程序直接引用,如果需要把加載委派給啟動(dòng)類(lèi)加載器瓦戚,直接使用 null 代替即可沮尿,因?yàn)閱?dòng)類(lèi)加載器通常由操作系統(tǒng)實(shí)現(xiàn),并不存在于 JVM 體系较解。

平臺(tái)類(lèi)加載器

從 JDK9 開(kāi)始從擴(kuò)展類(lèi)加載器更換為平臺(tái)類(lèi)加載器蛹找,負(fù)載加載一些擴(kuò)展的系統(tǒng)類(lèi),比如 XML哨坪、加密庸疾、壓縮相關(guān)的功能類(lèi)等。

應(yīng)用類(lèi)加載器

也稱系統(tǒng)類(lèi)加載器当编,負(fù)責(zé)加載用戶類(lèi)路徑上的類(lèi)庫(kù)届慈,可以直接在代碼中使用。如果沒(méi)有自定義類(lèi)加載器忿偷,一般情況下應(yīng)用類(lèi)加載器就是默認(rèn)的類(lèi)加載器金顿。自定義類(lèi)加載器通過(guò)繼承 ClassLoader 并重寫(xiě) findClass 方法實(shí)現(xiàn)。


雙親委派模型

雙親委派模型要求除了頂層的啟動(dòng)類(lèi)加載器外鲤桥,其余類(lèi)加載器都應(yīng)該有自己的父加載器揍拆。

一個(gè)類(lèi)加載器收到了類(lèi)加載請(qǐng)求,不會(huì)自己去嘗試加載茶凳,而將該請(qǐng)求委派給父加載器嫂拴,每層的類(lèi)加載器都是如此播揪,因此所有加載請(qǐng)求最終都應(yīng)該傳送到啟動(dòng)類(lèi)加載器,只有當(dāng)父加載器反饋無(wú)法完成請(qǐng)求時(shí)筒狠,子加載器才會(huì)嘗試猪狈。

類(lèi)跟隨它的加載器一起具備了有優(yōu)先級(jí)的層次關(guān)系,確保某個(gè)類(lèi)在各個(gè)類(lèi)加載器環(huán)境中都是同一個(gè)辩恼,保證程序的穩(wěn)定性雇庙。


判斷兩個(gè)類(lèi)是否相等

任意一個(gè)類(lèi)都必須由類(lèi)加載器和這個(gè)類(lèi)本身共同確立其在虛擬機(jī)中的唯一性。兩個(gè)類(lèi)只有由同一類(lèi)加載器加載才有比較意義灶伊,否則即使兩個(gè)類(lèi)來(lái)源于同一個(gè) Class 文件疆前,被同一個(gè) JVM 加載,只要類(lèi)加載器不同聘萨,這兩個(gè)類(lèi)就必定不相等竹椒。


并發(fā)

JMM

JMM?

Java 線程的通信由 JMM 控制,JMM 定義了變量的訪問(wèn)規(guī)則匈挖,變量包括實(shí)例字段、靜態(tài)字段康愤,但不包括局部變量儡循、方法參數(shù)這些線程私有的值。JMM 基本原則:只要不改變程序執(zhí)行結(jié)果征冷,編譯器和處理器怎么優(yōu)化都行择膝,例如某個(gè)鎖只會(huì)單線程訪問(wèn)就消除鎖,某個(gè) volatile 變量只會(huì)單線程訪問(wèn)就當(dāng)作普通變量检激。

JMM 規(guī)定所有變量都存儲(chǔ)在主內(nèi)存肴捉,每條線程有自己的工作內(nèi)存,工作內(nèi)存中保存變量的主內(nèi)存副本叔收,線程對(duì)變量的所有操作都必須在工作內(nèi)存進(jìn)行齿穗,不能直接讀寫(xiě)主內(nèi)存。

關(guān)于主內(nèi)存與工作內(nèi)存的交互饺律,即變量如何從主內(nèi)存拷貝到工作內(nèi)存窃页、從工作內(nèi)存同步回主內(nèi)存,JMM 定義了 8 種原子操作:

操作 作用范圍 作用
lock 主內(nèi)存 把變量標(biāo)識(shí)為鎖定狀態(tài)
unlock 主內(nèi)存 釋放鎖定狀態(tài)的變量
read 主內(nèi)存 把變量值從主內(nèi)存讀到工作內(nèi)存
load 工作內(nèi)存 把 read 值加載到工作內(nèi)存
use 工作內(nèi)存 把 load 值傳給執(zhí)行引擎
assign 工作內(nèi)存 把 use 值賦給工作內(nèi)存變量
store 工作內(nèi)存 把 assign 值傳到主內(nèi)存
write 主內(nèi)存 把 store 值寫(xiě)回主內(nèi)存變量

happens-before

先行發(fā)生原則复濒,指兩項(xiàng)操作間的偏序關(guān)系脖卖。JMM 存在一些天然的 happens-before 關(guān)系,如果兩個(gè)操作的關(guān)系不在此列且無(wú)法從中推導(dǎo)巧颈,虛擬機(jī)就可以對(duì)其重排序:

  • 一個(gè)線程內(nèi)寫(xiě)在前面的操作先行發(fā)生于后面的畦木。
  • unlock 先行發(fā)生于后面對(duì)同一個(gè)鎖的 lock。
  • 對(duì) volatile 變量的寫(xiě)先行發(fā)生于后面的讀砸泛。
  • 線程的 start 方法先行發(fā)生于線程的每個(gè)動(dòng)作十籍。
  • 線程中所有操作先行發(fā)生于對(duì)線程的終止檢測(cè)蛆封。
  • 對(duì)象的初始化先行發(fā)生于 finalize 方法。
  • 如果 A 先行發(fā)生于 B妓雾,B 先行發(fā)生于 C娶吞,那么 A 先行發(fā)生于 C 。

as-if-serial 保證單線程程序的執(zhí)行結(jié)果不變械姻,happens-before 保證正確同步的多線程程序的執(zhí)行結(jié)果不變妒蛇。這兩種語(yǔ)義的目的都是為了在不改變程序執(zhí)行結(jié)果的前提下盡可能提高程序并行度。


重排序

為了提高性能楷拳,編譯器和處理器通常會(huì)對(duì)指令進(jìn)行重排序绣夺,重排序指從源代碼到指令序列的重排序,分為三種:① 編譯器重排序欢揖,編譯器在不改變單線程程序語(yǔ)義的前提下可以重排語(yǔ)句的執(zhí)行順序陶耍。② 處理器重排序,如果不存在數(shù)據(jù)依賴性她混,處理器可以改變語(yǔ)句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序烈钞。③ 內(nèi)存系統(tǒng)的重排序。


原子性坤按、可見(jiàn)性毯欣、有序性

原子性

原子性指操作要么全部成功,要么全部失敗臭脓⌒锍基本數(shù)據(jù)類(lèi)型的訪問(wèn)都具備原子性,例外就是 long 和 double来累,虛擬機(jī)將沒(méi)有被 volatile 修飾的 64 位數(shù)據(jù)操作劃分為兩次 32 位操作砚作。

可見(jiàn)性

可見(jiàn)性指當(dāng)一個(gè)線程修改了共享變量時(shí),其他線程能夠立即得知修改嘹锁。

volatile:保證新值能立即同步到主內(nèi)存以及每次使用前立即從主內(nèi)存刷新葫录。

synchronized:對(duì)一個(gè)變量執(zhí)行 unlock 前必須先把此變量同步回主內(nèi)存,即先執(zhí)行 store 和 write领猾。

final:final 字段在構(gòu)造方法中一旦初始化完成压昼,并且構(gòu)造方法沒(méi)有把 this 引用傳遞出去,其他線程就能看到 final 字段的值瘤运。

有序性

volatile 和 synchronized 保證有序性窍霞,volatile 本身就包含禁止指令重排序的語(yǔ)義,而 synchronized 保證一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行 lock 操作拯坟。


volatile?

volatile 變量特性:

  • 保證變量對(duì)所有線程可見(jiàn)

    當(dāng)一條線程修改了變量值但金,新值對(duì)于其他線程來(lái)說(shuō)立即可見(jiàn)。但 Java 運(yùn)算并非原子操作郁季,導(dǎo)致 volatile 變量運(yùn)算在并發(fā)下仍不安全冷溃。

  • 禁止指令重排序優(yōu)化

    使用 volatile 變量進(jìn)行寫(xiě)操作钱磅,匯編指令帶有 lock 前綴,相當(dāng)于一個(gè)內(nèi)存屏障似枕,后面的指令不能重排到屏障之前盖淡。

    lock 引發(fā)兩件事:① 將當(dāng)前處理器緩存行的數(shù)據(jù)寫(xiě)回系統(tǒng)內(nèi)存。②使其他處理器的緩存無(wú)效凿歼。相當(dāng)于對(duì)緩存變量做了一次 store 和 write 操作褪迟,讓 volatile 變量的修改對(duì)其他處理器立即可見(jiàn)。

靜態(tài)變量 i++ 的不安全問(wèn)題

自增語(yǔ)句由 4 條字節(jié)碼指令構(gòu)成的答憔,依次為 getstatic味赃、iconst_1iadd虐拓、putstatic心俗,當(dāng) getstatic 把 i 取到操作棧頂時(shí),volatile 保證了 i 值在此刻正確蓉驹,但在執(zhí)行 iconst_1城榛、iadd 時(shí),其他線程可能已經(jīng)改變了 i 值态兴,操作棧頂?shù)闹稻妥兂闪伺K數(shù)據(jù)狠持,所以 putstatic 后就可能把較小的值同步回了主內(nèi)存。

適用場(chǎng)景

運(yùn)算結(jié)果不依賴變量的當(dāng)前值诗茎;一寫(xiě)多讀工坊,只有單一的線程修改變量值献汗。

內(nèi)存語(yǔ)義

寫(xiě) volatile 變量時(shí)敢订,把該線程工作內(nèi)存中的值刷新到主內(nèi)存;讀 volatile 變量時(shí)罢吃,把該線程工作內(nèi)存值置為無(wú)效楚午,從主內(nèi)存讀取。

在舊的內(nèi)存模型中尿招,不允許 volatile 變量間重排序矾柜,但允許 volatile 變量與普通變量重排序,可能導(dǎo)致內(nèi)存不可見(jiàn)問(wèn)題就谜。JSR-133 嚴(yán)格限制編譯器和處理器對(duì) volatile 變量與普通變量的重排序怪蔑,確保 volatile 的寫(xiě)-讀和鎖的釋放-獲取具有相同的內(nèi)存語(yǔ)義。


final?

final 類(lèi)不能被繼承丧荐,所有成員方法都會(huì)被隱式地指定為 final 方法缆瓣,final 方法不能被重寫(xiě)。

final 變量表示常量虹统,只能被賦值一次弓坞,賦值后值不再改變隧甚。

  • 修飾基本數(shù)據(jù)類(lèi)型時(shí),該值在初始化后不能改變渡冻。
  • 修飾引用類(lèi)型時(shí)戚扳,引用指向的對(duì)象在初始化后不能改變,但該對(duì)象的內(nèi)容可以發(fā)生變化族吻。

內(nèi)存語(yǔ)義

  • 編譯器會(huì)在 final 域的寫(xiě)后帽借,構(gòu)造方法的 return 前插入一個(gè) Store Store 屏障,確保對(duì)象引用為任意線程可見(jiàn)前其 final 域已初始化呼奢。

  • 編譯器在讀 final 域操作的前面插入一個(gè) Load Load 屏障宜雀,確保在讀一個(gè)對(duì)象的 final 域前一定會(huì)先讀包含這個(gè) final 域的對(duì)象引用。


synchronized?

每個(gè) Java 對(duì)象都有一個(gè)關(guān)聯(lián)的 monitor握础,JVM 會(huì)根據(jù) monitor 的狀態(tài)進(jìn)行加解鎖的判斷辐董,monitor 在被釋放前不能被其他線程獲取。

同步塊使用 monitorentermonitorexit 字節(jié)碼指令獲取和釋放 monitor禀综。這兩個(gè)指令都需要一個(gè)引用類(lèi)型的參數(shù)指明鎖對(duì)象简烘,對(duì)于普通方法,鎖是當(dāng)前實(shí)例對(duì)象定枷;對(duì)于靜態(tài)方法孤澎,鎖是當(dāng)前類(lèi)的 Class 對(duì)象;對(duì)于方法塊欠窒,鎖是 synchronized 括號(hào)里的對(duì)象覆旭。

執(zhí)行 monitorenter 指令時(shí),首先嘗試獲取對(duì)象鎖岖妄。如果這個(gè)對(duì)象沒(méi)有被鎖定型将,或當(dāng)前線程已經(jīng)持有鎖,就把鎖的計(jì)數(shù)器加 1荐虐,執(zhí)行 monitorexit 指令時(shí)會(huì)將鎖計(jì)數(shù)器減 1七兜。一旦計(jì)數(shù)器為 0 鎖隨即就被釋放。

假設(shè)有兩個(gè)線程 A福扬、B 競(jìng)爭(zhēng)鎖腕铸,當(dāng) A 競(jìng)爭(zhēng)到鎖時(shí)會(huì)將 monitor 中的 owner 設(shè)置為 A,把 B 阻塞并放到等待資源的 ContentionList 隊(duì)列铛碑。ContentionList 中的部分線程會(huì)進(jìn)入 EntryList狠裹,EntryList 中的線程會(huì)被指定為 OnDeck 競(jìng)爭(zhēng)候選者,如果獲得了鎖資源將進(jìn)入 Owner 狀態(tài)汽烦,釋放鎖后進(jìn)入 !Owner 狀態(tài)涛菠。

synchronized 修飾的同步塊是可重入的,并且持有鎖的線程釋放鎖前會(huì)阻塞其他線程。持有鎖是一個(gè)重量級(jí)的操作碗暗,Java 線程是映射到操作系統(tǒng)的內(nèi)核線程上的颈将,如果要阻塞或喚醒一條線程,需要用戶態(tài)到核心態(tài)的轉(zhuǎn)換言疗。

不公平性

所有收到鎖請(qǐng)求的線程首先自旋晴圾,如果通過(guò)自旋也沒(méi)有獲取鎖將被放入 ContentionList,該做法對(duì)于已經(jīng)進(jìn)入隊(duì)列的線程不公平噪奄。

為了防止 ContentionList 隊(duì)列尾部的元素被大量線程 CAS 訪問(wèn)影響性能死姚,Owner 線程會(huì)在釋放鎖時(shí)將隊(duì)列的部分線程移動(dòng)到 EntryList 并指定某個(gè)線程為 OnDeck 線程。


鎖優(yōu)化策略?

JDK 6 對(duì) synchronized 做了很多優(yōu)化勤篮,引入了自適應(yīng)自旋都毒、鎖消除、鎖粗化碰缔、偏向鎖和輕量級(jí)鎖等提高鎖的效率账劲,鎖一共有 4 個(gè)狀態(tài),級(jí)別從低到高依次是:無(wú)鎖金抡、偏向鎖瀑焦、輕量級(jí)鎖和重量級(jí)鎖,狀態(tài)會(huì)隨競(jìng)爭(zhēng)升級(jí)梗肝,但不能降級(jí)榛瓮。


自適應(yīng)自旋

許多鎖定時(shí)間很短童谒,為了這段時(shí)間去掛起和恢復(fù)線程并不劃算铝穷。如果機(jī)器有多個(gè)處理器核心鼻百,可以讓后面請(qǐng)求鎖的線程稍等一會(huì)涌萤,但不放棄處理器的執(zhí)行時(shí)間,看看鎖是否很快會(huì)被釋放瘫絮,這就是自旋鎖扮念。

自旋鎖在 JDK1.4 引入突雪,默認(rèn)關(guān)閉什黑,在 JDK6 默認(rèn)開(kāi)啟崎淳。如果自旋超過(guò)限定次數(shù)仍然沒(méi)有成功堪夭,就應(yīng)掛起線程愕把,自旋默認(rèn)限定次數(shù)是 10。

JDK6 對(duì)自旋鎖進(jìn)行了優(yōu)化森爽,自旋時(shí)間不再固定恨豁,而是由前一次的自旋時(shí)間及鎖擁有者的狀態(tài)決定。

如果在同一個(gè)鎖上爬迟,自旋剛剛成功獲得過(guò)鎖且持有鎖的線程正在運(yùn)行橘蜜,虛擬機(jī)會(huì)認(rèn)為這次自旋也很可能成功,允許自旋持續(xù)更久。如果自旋很少成功计福,以后獲取鎖時(shí)將可能直接省略掉自旋跌捆,避免浪費(fèi)處理器資源。


鎖消除

鎖消除指即時(shí)編譯器對(duì)檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行消除象颖。如果堆上的所有數(shù)據(jù)都只被一個(gè)線程訪問(wèn)佩厚,就當(dāng)作棧上的數(shù)據(jù)對(duì)待,認(rèn)為它們是線程私有的而無(wú)須同步说订。


鎖粗化

原則上需要將同步塊的作用范圍限制得盡量小抄瓦,只在共享數(shù)據(jù)的實(shí)際作用域進(jìn)行同步,使等待鎖的線程盡快拿到鎖陶冷。

但如果一系列的連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖钙姊,即使沒(méi)有線程競(jìng)爭(zhēng)也會(huì)導(dǎo)致不必要的性能消耗。因此如果虛擬機(jī)探測(cè)到有一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖埂伦,會(huì)把同步范圍擴(kuò)展到整個(gè)操作序列的外部煞额。


偏向鎖

偏向鎖是為了在沒(méi)有競(jìng)爭(zhēng)的情況下減少鎖開(kāi)銷(xiāo),鎖會(huì)偏向于第一個(gè)獲得它的線程沾谜,如果在執(zhí)行過(guò)程中鎖一直沒(méi)有被其他線程獲取立镶,則持有偏向鎖的線程將不需要同步。

當(dāng)鎖對(duì)象第一次被線程獲取時(shí)类早,虛擬機(jī)會(huì)將對(duì)象頭中的偏向模式設(shè)為 1媚媒,同時(shí)使用 CAS 把獲取到鎖的線程 ID 記錄在對(duì)象的 Mark Word 中。如果 CAS 成功涩僻,持有偏向鎖的線程以后每次進(jìn)入鎖相關(guān)的同步塊都不再進(jìn)行任何同步操作缭召。

一旦有其他線程嘗試獲取鎖,偏向模式立即結(jié)束逆日,根據(jù)鎖對(duì)象是否處于鎖定狀態(tài)決定是否撤銷(xiāo)偏向嵌巷,后續(xù)同步按照輕量級(jí)鎖的步驟執(zhí)行。


輕量級(jí)鎖

輕量級(jí)鎖是為了在沒(méi)有競(jìng)爭(zhēng)的前提下減少重量級(jí)鎖的性能消耗室抽。

在代碼即將進(jìn)入同步塊時(shí)搪哪,如果同步對(duì)象沒(méi)有被鎖定,虛擬機(jī)將在當(dāng)前線程的棧幀中建立一個(gè)鎖記錄空間坪圾,存儲(chǔ)鎖對(duì)象目前 Mark Word 的拷貝晓折。然后使用 CAS 嘗試把對(duì)象的 Mark Word 更新為指向鎖記錄的指針,如果更新成功代表該線程擁有了鎖兽泄,鎖標(biāo)志位將轉(zhuǎn)變?yōu)?00漓概,表示輕量級(jí)鎖狀態(tài)。

如果更新失敗就意味著存在線程競(jìng)爭(zhēng)病梢。虛擬機(jī)檢查對(duì)象的 Mark Word 是否指向當(dāng)前線程的棧幀胃珍,如果是則說(shuō)明當(dāng)前線程已經(jīng)擁有了鎖,直接進(jìn)入同步塊繼續(xù)執(zhí)行,否則說(shuō)明鎖已被其他線程搶占觅彰。如果出現(xiàn)兩條以上線程競(jìng)爭(zhēng)鎖吩蔑,輕量級(jí)鎖將膨脹為重量級(jí)鎖,鎖標(biāo)志狀態(tài)變?yōu)?10填抬,此時(shí)Mark Word 存儲(chǔ)的就是指向重量級(jí)鎖的指針哥纫,后面等待鎖的線程將阻塞。

解鎖同樣通過(guò) CAS 進(jìn)行痴奏,如果對(duì)象 Mark Word 仍然指向線程的鎖記錄蛀骇,就用 CAS 把對(duì)象當(dāng)前的 Mark Word 和線程復(fù)制的 Mark Word 替換回來(lái)。假如替換成功同步過(guò)程就完成了读拆,否則說(shuō)明有其他線程嘗試過(guò)獲取該鎖擅憔,就要在釋放鎖的同時(shí)喚醒被掛起的線程。


偏向鎖檐晕、輕量級(jí)鎖和重量級(jí)鎖的區(qū)別

偏向鎖的優(yōu)點(diǎn)是加解鎖不需要額外消耗暑诸,和執(zhí)行非同步方法比僅存在納秒級(jí)差距,適用只有一個(gè)線程訪問(wèn)同步代碼塊的場(chǎng)景辟灰。

輕量級(jí)鎖的優(yōu)點(diǎn)是程序響應(yīng)速度快个榕,缺點(diǎn)是如果線程始終得不到鎖會(huì)自旋消耗 CPU,適用追求響應(yīng)時(shí)間芥喇、同步代碼塊執(zhí)行快的場(chǎng)景西采。

重量級(jí)鎖的優(yōu)點(diǎn)是線程競(jìng)爭(zhēng)不使用自旋不消耗CPU,缺點(diǎn)是線程會(huì)阻塞继控,響應(yīng)時(shí)間慢械馆,適應(yīng)追求吞吐量、同步代碼塊執(zhí)行慢的場(chǎng)景武通。


Lock

Lock 是 juc 包的頂層接口霹崎,擺脫了語(yǔ)言特性束縛,在類(lèi)庫(kù)層面實(shí)現(xiàn)同步冶忱,利用了 volatile 的可見(jiàn)性尾菇。

重入鎖 ReentrantLock 是 Lock 最常見(jiàn)的實(shí)現(xiàn),與 synchronized 一樣可重入囚枪,不過(guò)它增加了一些功能:

  • **等待可中斷: **持有鎖的線程長(zhǎng)期不釋放鎖時(shí)派诬,正在等待的線程可以選擇放棄等待而處理其他事情。
  • 公平鎖: synchronized 是非公平的眶拉,ReentrantLock 默認(rèn)是非公平的千埃,可以通過(guò)構(gòu)造方法指定公平鎖憔儿。
  • 鎖綁定多個(gè)條件: 一個(gè) ReentrantLock 可以同時(shí)綁定多個(gè) Condition忆植。synchronized 中鎖對(duì)象的 waitnotify 只可以實(shí)現(xiàn)一個(gè)隱含條件,而 ReentrantLock 可以調(diào)用 newCondition 創(chuàng)建多個(gè)條件。

一般優(yōu)先考慮 synchronized:① synchronized 是語(yǔ)法層面的同步朝刊,足夠簡(jiǎn)單耀里。② Lock 必須手動(dòng)在 finally 釋放鎖,而synchronized 可以由 JVM 來(lái)確保即使出現(xiàn)異常也能正常釋放鎖拾氓。③ 盡管 JDK5 時(shí) ReentrantLock 的性能優(yōu)于 synchronized冯挎,但 JDK6 鎖優(yōu)化后二者的性能基本持平。 JVM 更可能針對(duì)synchronized 優(yōu)化咙鞍,因?yàn)?JVM 可以在線程和對(duì)象的元數(shù)據(jù)中記錄鎖的相關(guān)信息房官。


ReentrantLock 的可重入實(shí)現(xiàn)

以非公平鎖為例,通過(guò) nonfairTryAcquire 方法獲取鎖续滋,如果是持有鎖的線程再次請(qǐng)求則增加同步狀態(tài)值并返回 true翰守。

釋放同步狀態(tài)時(shí)將減少同步狀態(tài)值。如果鎖被獲取了 n 次疲酌,那么前 n-1 次 tryRelease 方法必須都返回 fasle蜡峰,只有同步狀態(tài)完全釋放才能返回 true,并將鎖占有線程設(shè)置為null朗恳。

公平鎖使用 tryAcquire 方法湿颅,該方法與非公平鎖的區(qū)別是:判斷條件中多了對(duì)同步隊(duì)列中當(dāng)前節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn)的判斷,如果該方法返回 true 表示有線程比當(dāng)前線程更早請(qǐng)求鎖粥诫,因此需要等待前驅(qū)線程油航。


讀寫(xiě)鎖

讀寫(xiě)鎖在同一時(shí)刻允許多個(gè)讀線程訪問(wèn),在寫(xiě)線程訪問(wèn)時(shí)怀浆,所有的讀寫(xiě)線程均阻塞劝堪。

讀寫(xiě)鎖依賴 AQS 實(shí)現(xiàn),在一個(gè) int 變量上維護(hù)讀線程和寫(xiě)線程的狀態(tài)揉稚,將變量切分成了兩個(gè)部分秒啦,高 16 位表示讀,低 16 位表示寫(xiě)搀玖。

寫(xiě)鎖是可重入排他鎖余境,如果當(dāng)前線程已經(jīng)獲得了寫(xiě)鎖則增加寫(xiě)狀態(tài),如果當(dāng)前線程在獲取寫(xiě)鎖時(shí)灌诅,讀鎖已經(jīng)被獲取或者該線程不是已經(jīng)獲得寫(xiě)鎖的線程則等待芳来。寫(xiě)鎖的釋放與 ReentrantLock 的釋放類(lèi)似,每次釋放減少寫(xiě)狀態(tài)猜拾,當(dāng)寫(xiě)狀態(tài)為 0 時(shí)表示寫(xiě)鎖已被釋放即舌。

讀鎖是可重入共享鎖,能夠被多個(gè)線程同時(shí)獲取挎袜。如果當(dāng)前線程已經(jīng)獲取了讀鎖顽聂,則增加讀狀態(tài)肥惭。如果當(dāng)前線程在獲取讀鎖時(shí),寫(xiě)鎖已被其他線程獲取則進(jìn)入等待紊搪。讀鎖每次釋放會(huì)減少讀狀態(tài)蜜葱,減少的值是 1<<16,讀鎖的釋放是線程安全的耀石。

鎖降級(jí)指把持住當(dāng)前寫(xiě)鎖再獲取讀鎖牵囤,隨后釋放先前擁有的寫(xiě)鎖。

鎖降級(jí)中讀鎖的獲取是必要的滞伟,這是為了保證數(shù)據(jù)可見(jiàn)性揭鳞,如果當(dāng)前線程不獲取讀鎖而直接釋放寫(xiě)鎖,假設(shè)另一個(gè)線程 A 獲取寫(xiě)鎖修改數(shù)據(jù)梆奈,當(dāng)前線程無(wú)法感知線程 A 的數(shù)據(jù)更新汹桦。


AQS

AQS 隊(duì)列同步器是用來(lái)構(gòu)建鎖或其他同步組件的基礎(chǔ)框架,使用一個(gè) volatile int 變量作為共享同步狀態(tài)鉴裹,如果線程獲取狀態(tài)失敗舞骆,則進(jìn)入同步隊(duì)列等待;如果成功就執(zhí)行臨界區(qū)代碼径荔,釋放狀態(tài)時(shí)會(huì)通知同步隊(duì)列中的等待線程督禽。

同步器的主要使用方式是繼承,子類(lèi)通過(guò)繼承同步器并實(shí)現(xiàn)它的抽象方法來(lái)管理同步狀態(tài)总处,對(duì)同步狀態(tài)進(jìn)行更改需要使用同步器提供的 3個(gè)方法 getState狈惫、setStatecompareAndSetState ,它們保證狀態(tài)改變是安全的鹦马。

每當(dāng)有新線程請(qǐng)求同步狀態(tài)時(shí)都會(huì)進(jìn)入一個(gè)等待隊(duì)列胧谈,等待隊(duì)列通過(guò)雙向鏈表實(shí)現(xiàn),線程被封裝在鏈表的 Node 節(jié)點(diǎn)中荸频,Node 的等待狀態(tài)包括:CANCELLED(線程已取消)菱肖、SIGNAL(線程需要喚醒)、CONDITION (線程正在等待)旭从、PROPAGATE(后繼節(jié)點(diǎn)會(huì)傳播喚醒操作稳强,只作用于共享模式)。


兩種模式

獨(dú)占模式下鎖只會(huì)被一個(gè)線程占用和悦,其他線程必須等持有鎖的線程釋放鎖后才能獲取鎖退疫。

獲取同步狀態(tài)時(shí),調(diào)用 acquire 方法的 tryAcquire 方法安全地獲取同步狀態(tài)鸽素,獲取失敗的線程會(huì)被構(gòu)造同步節(jié)點(diǎn)并通過(guò) addWaiter 方法加入到同步隊(duì)列的尾部褒繁,在隊(duì)列中自旋。之后調(diào)用 acquireQueued 方法使得節(jié)點(diǎn)以死循環(huán)的方式獲取同步狀態(tài)馍忽,如果獲取不到則阻塞棒坏,被阻塞線程的喚醒依靠前驅(qū)節(jié)點(diǎn)的出隊(duì)或中斷燕差。后繼節(jié)點(diǎn)的線程被喚醒后需要檢查自己的前驅(qū)節(jié)點(diǎn)是否是頭節(jié)點(diǎn),目的是維護(hù)同步隊(duì)列的 FIFO 原則俊抵,節(jié)點(diǎn)之間在循環(huán)檢查的過(guò)程中基本不通信谁不,而是簡(jiǎn)單判斷自己的前驅(qū)是否為頭節(jié)點(diǎn)坐梯。

釋放同步狀態(tài)時(shí)徽诲,同步器調(diào)用 tryRelease 方法釋放同步狀態(tài),然后調(diào)用 unparkSuccessor 方法喚醒頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)吵血,使后繼節(jié)點(diǎn)重新嘗試獲取同步狀態(tài)谎替。

共享模式下多個(gè)線程可以獲取同一個(gè)鎖。

獲取同步狀態(tài)時(shí)蹋辅,調(diào)用 acquireShared 方法的 tryAcquireShared 方法钱贯,返回值為 int 類(lèi)型,值不小于 0 表示能獲取同步狀態(tài)侦另。

釋放同步狀態(tài)時(shí)秩命,調(diào)用 releaseShared 方法,釋放后會(huì)喚醒后續(xù)處于等待狀態(tài)的節(jié)點(diǎn)褒傅。它和獨(dú)占式的區(qū)別在于 tryReleaseShared 方法必須確保同步狀態(tài)安全釋放弃锐,通過(guò)循環(huán) CAS 保證。


線程

生命周期?

NEW:新建狀態(tài)殿托,線程被創(chuàng)建且未啟動(dòng)霹菊,還未調(diào)用 start 方法。

RUNNABLE:Java 將操作系統(tǒng)中的就緒和運(yùn)行兩種狀態(tài)統(tǒng)稱為 RUNNABLE支竹,此時(shí)線程有可能在等待時(shí)間片旋廷,也有可能在執(zhí)行。

BLOCKED:阻塞狀態(tài)礼搁,可能由于鎖被其他線程占用饶碘、調(diào)用了 sleepjoin 方法、執(zhí)行了 wait方法等馒吴。

WAITING:等待狀態(tài)熊镣,該狀態(tài)線程不會(huì)被分配 CPU 時(shí)間片,需要其他線程通知或中斷募书⌒鞔眩可能由于調(diào)用了無(wú)參的 waitjoin 方法。

TIME_WAITING:限期等待狀態(tài)莹捡,可以在指定時(shí)間內(nèi)自行返回鬼吵。導(dǎo)可能由于調(diào)用了帶參的 waitjoin 方法。

TERMINATED:終止?fàn)顟B(tài)篮赢,表示當(dāng)前線程已執(zhí)行完畢或異常退出齿椅。


線程的創(chuàng)建方式?

① 繼承 Thread 類(lèi)并重寫(xiě) run 方法琉挖。實(shí)現(xiàn)簡(jiǎn)單,但不符合里氏替換原則涣脚,不可以繼承其他類(lèi)示辈。

② 實(shí)現(xiàn) Runnable 接口并重寫(xiě) run 方法。避免了單繼承局限性遣蚀,實(shí)現(xiàn)解耦矾麻。

③實(shí)現(xiàn) Callable 接口并重寫(xiě) call 方法“盘荩可以獲取線程執(zhí)行結(jié)果的返回值险耀,并且可以拋出異常。


線程方法

sleep 方法導(dǎo)致當(dāng)前線程進(jìn)入休眠狀態(tài)玖喘,與 wait 不同的是該方法不會(huì)釋放鎖資源甩牺,進(jìn)入的是 TIME-WAITING 狀態(tài)。

yiled 方法使當(dāng)前線程讓出 CPU 時(shí)間片給優(yōu)先級(jí)相同或更高的線程累奈,回到 RUNNABLE 狀態(tài)贬派,與其他線程重新競(jìng)爭(zhēng) CPU 時(shí)間片。

join 方法用于等待其他線程運(yùn)行終止澎媒,如果當(dāng)前線程調(diào)用了另一個(gè)線程的 join 方法搞乏,則當(dāng)前線程進(jìn)入阻塞狀態(tài),當(dāng)另一個(gè)線程結(jié)束時(shí)當(dāng)前線程才能從阻塞狀態(tài)轉(zhuǎn)為就緒態(tài)旱幼,等待獲取CPU時(shí)間片查描。底層使用 wait,也會(huì)釋放鎖柏卤。


守護(hù)線程

守護(hù)線程是一種支持型線程冬三,可以通過(guò) setDaemon(true) 將線程設(shè)置為守護(hù)線程,但必須在線程啟動(dòng)前設(shè)置缘缚。

守護(hù)線程用于完成支持性工作勾笆,但在 JVM 退出時(shí)守護(hù)線程中的 finally 塊不一定執(zhí)行,因?yàn)?JVM 中沒(méi)有非守護(hù)線程時(shí)需要立即退出桥滨,因此不能靠在守護(hù)線程使用 finally 確保關(guān)閉資源窝爪。


線程通信的方式?

Java 采用共享內(nèi)存模型,線程間的通信總是隱式進(jìn)行齐媒,整個(gè)通信過(guò)程對(duì)程序員完全透明蒲每。

volatile 告知程序任何對(duì)變量的讀需要從主內(nèi)存中獲取,寫(xiě)必須同步刷新回主內(nèi)存喻括,保證所有線程對(duì)變量訪問(wèn)的可見(jiàn)性邀杏。

synchronized 確保多個(gè)線程在同一時(shí)刻只能有一個(gè)處于方法或同步塊中,保證線程對(duì)變量訪問(wèn)的原子性唬血、可見(jiàn)性和有序性望蜡。

等待通知機(jī)制指一個(gè)線程 A 調(diào)用了對(duì)象的 wait 方法進(jìn)入等待狀態(tài)唤崭,另一線程 B 調(diào)用了對(duì)象的 notify/notifyAll 方法,線程 A 收到通知后結(jié)束阻塞并執(zhí)行后序操作脖律。對(duì)象上的 waitnotify/notifyAll 完成等待方和通知方的交互谢肾。

如果一個(gè)線程執(zhí)行了某個(gè)線程的 join 方法,這個(gè)線程就會(huì)阻塞等待執(zhí)行了 join 方法的線程終止小泉,這里涉及等待/通知機(jī)制芦疏。join 底層通過(guò) wait 實(shí)現(xiàn),線程終止時(shí)會(huì)調(diào)用自身的 notifyAll 方法膏孟,通知所有等待在該線程對(duì)象上的線程眯分。

管道 IO 流用于線程間數(shù)據(jù)傳輸拌汇,媒介為內(nèi)存柒桑。PipedOutputStream 和 PipedWriter 是輸出流,相當(dāng)于生產(chǎn)者噪舀,PipedInputStream 和 PipedReader 是輸入流魁淳,相當(dāng)于消費(fèi)者。管道流使用一個(gè)默認(rèn)大小為 1KB 的循環(huán)緩沖數(shù)組与倡。輸入流從緩沖數(shù)組讀數(shù)據(jù)界逛,輸出流往緩沖數(shù)組中寫(xiě)數(shù)據(jù)。當(dāng)數(shù)組已滿時(shí)纺座,輸出流所在線程阻塞息拜;當(dāng)數(shù)組首次為空時(shí),輸入流所在線程阻塞净响。

ThreadLocal 是線程共享變量少欺,但它可以為每個(gè)線程創(chuàng)建單獨(dú)的副本,副本值是線程私有的馋贤,互相之間不影響赞别。


線程池好處

降低資源消耗,復(fù)用已創(chuàng)建的線程配乓,降低開(kāi)銷(xiāo)仿滔、控制最大并發(fā)數(shù)。

隔離線程環(huán)境犹芹,可以配置獨(dú)立線程池崎页,將較慢的線程與較快的隔離開(kāi),避免相互影響腰埂。

實(shí)現(xiàn)任務(wù)線程隊(duì)列緩沖策略和拒絕機(jī)制飒焦。

實(shí)現(xiàn)某些與時(shí)間相關(guān)的功能,如定時(shí)執(zhí)行盐固、周期執(zhí)行等荒给。


線程池處理任務(wù)的流程

① 核心線程池未滿丈挟,創(chuàng)建一個(gè)新的線程執(zhí)行任務(wù),此時(shí) workCount < corePoolSize志电。

② 如果核心線程池已滿曙咽,工作隊(duì)列未滿,將線程存儲(chǔ)在工作隊(duì)列挑辆,此時(shí) workCount >= corePoolSize例朱。

③ 如果工作隊(duì)列已滿,線程數(shù)小于最大線程數(shù)就創(chuàng)建一個(gè)新線程處理任務(wù)鱼蝉,此時(shí) workCount < maximumPoolSize洒嗤,這一步也需要獲取全局鎖。

④ 如果超過(guò)大小線程數(shù),按照拒絕策略來(lái)處理任務(wù)患民,此時(shí) workCount > maximumPoolSize怠肋。

線程池會(huì)將線程封裝成工作線程 Worker,Worker 在執(zhí)行完任務(wù)后會(huì)循環(huán)獲取工作隊(duì)列中的任務(wù)來(lái)執(zhí)行间唉。


創(chuàng)建線程池

可以通過(guò) Executors 的靜態(tài)工廠方法創(chuàng)建線程池:

newFixedThreadPool,固定大小的線程池利术,核心線程數(shù)也是最大線程數(shù)呈野,不存在空閑線程,keepAliveTime = 0印叁。使用的工作隊(duì)列是無(wú)界阻塞隊(duì)列 LinkedBlockingQueue被冒,適用于負(fù)載較重的服務(wù)器。

newSingleThreadExecutor轮蜕,使用單線程昨悼,相當(dāng)于單線程串行執(zhí)行所有任務(wù),適用于需要保證順序執(zhí)行任務(wù)的場(chǎng)景肠虽。

newCachedThreadPool幔戏,最大線程數(shù)是 Integer 最大值,使用的工作隊(duì)列是沒(méi)有容量的 SynchronousQueue税课,如果主線程提交任務(wù)的速度高于線程處理的速度闲延,線程池會(huì)不斷創(chuàng)建新線程,極端情況下會(huì)耗盡 CPU 和內(nèi)存資源韩玩。適用于執(zhí)行很多短期異步任務(wù)的小程序或負(fù)載較輕的服務(wù)器垒玲。

newScheduledThreadPool:最大線程數(shù)是Integer 最大值,存在 OOM 風(fēng)險(xiǎn)找颓。支持定期及周期性任務(wù)執(zhí)行合愈,適用需要多個(gè)后臺(tái)執(zhí)行任務(wù),同時(shí)限制線程數(shù)量的場(chǎng)景。相比 Timer 更安全佛析,功能更強(qiáng)益老,與 newCachedThreadPool 的區(qū)別是不回收工作線程。


線程池的參數(shù)

① corePoolSize:常駐核心線程數(shù)寸莫,設(shè)置過(guò)大會(huì)浪費(fèi)資源捺萌,過(guò)小會(huì)導(dǎo)致線程的頻繁創(chuàng)建與銷(xiāo)毀。

② maximumPoolSize:線程池能夠容納同時(shí)執(zhí)行的線程最大數(shù)膘茎,必須大于 0桃纯。

③ keepAliveTime:線程空閑時(shí)間,線程空閑時(shí)間達(dá)到該值后會(huì)被銷(xiāo)毀披坏,直到線程數(shù)等于 corePoolSize 為止态坦,避免浪費(fèi)內(nèi)存資源。

④ unit:keepAliveTime 的時(shí)間單位棒拂。

⑤ workQueue:工作隊(duì)列伞梯,當(dāng)線程請(qǐng)求數(shù)大于等于 corePoolSize 時(shí)線程會(huì)進(jìn)入隊(duì)列。

⑥ threadFactory:線程工廠着茸,用來(lái)生產(chǎn)一組相同任務(wù)的線程壮锻∷雠裕可以給線程命名涮阔,有利于分析錯(cuò)誤。

⑦ handler:拒絕策略灰殴,默認(rèn)使用 AbortPolicy 丟棄任務(wù)并拋出異常敬特,CallerRunsPolicy 重新嘗試提交任務(wù),DiscardOldestPolicy 拋棄隊(duì)列里等待最久的任務(wù)并把當(dāng)前任務(wù)加入隊(duì)列牺陶,DiscardPolicy 丟棄任務(wù)但不拋出異常伟阔。


關(guān)閉線程池

可以調(diào)用 shutdownshutdownNow 方法關(guān)閉線程池,原理是遍歷線程池中的工作線程掰伸,逐個(gè)調(diào)用 interrupt 方法中斷線程皱炉。

區(qū)別是 shutdownNow 首先將線程池的狀態(tài)設(shè)為 STOP,然后嘗試停止正在執(zhí)行或暫停任務(wù)的線程狮鸭,并返回等待執(zhí)行任務(wù)的列表合搅。而 shutdown 只是將線程池的狀態(tài)設(shè)為 SHUTDOWN,然后中斷沒(méi)有正在執(zhí)行任務(wù)的線程歧蕉。

通常調(diào)用 shutdown 來(lái)關(guān)閉線程池灾部,如果任務(wù)不一定要執(zhí)行完可調(diào)用 shutdownNow


線程池的選擇策略

  • 任務(wù)性質(zhì):CPU 密集型惯退、IO 密集型和混合型赌髓。

    性質(zhì)不同的任務(wù)用不同規(guī)模的線程池處理,CPU 密集型任務(wù)應(yīng)配置盡可能少的線程;IO 密集型任務(wù)應(yīng)配置盡可能多的線程锁蠕;混合型任務(wù)夷野,如果可以拆分,將其拆分為一個(gè) CPU 密集型任務(wù)和一個(gè) IO 密集型任務(wù)荣倾,只要兩個(gè)任務(wù)執(zhí)行時(shí)間相差不大扫责,那么分解后的吞吐量將高于串行執(zhí)行的吞吐量,如果相差太大則沒(méi)必要分解逃呼。

  • 任務(wù)優(yōu)先級(jí)/執(zhí)行時(shí)間鳖孤。

    使用優(yōu)先級(jí)隊(duì)列讓優(yōu)先級(jí)高或執(zhí)行時(shí)間短的任務(wù)先執(zhí)行。

  • 任務(wù)依賴性:是否依賴其他資源抡笼,如數(shù)據(jù)庫(kù)連接苏揣。

    依賴數(shù)據(jù)庫(kù)連接池的任務(wù),由于線程提交 SQL 后需要等待數(shù)據(jù)庫(kù)返回的結(jié)果推姻,等待的時(shí)間越長(zhǎng) CPU 空閑的時(shí)間就越長(zhǎng)平匈,因此線程數(shù)應(yīng)該盡可能地設(shè)置大一些,提高 CPU 的利用率藏古。


阻塞隊(duì)列

阻塞隊(duì)列支持阻塞插入和移除增炭,當(dāng)隊(duì)列滿時(shí),阻塞生產(chǎn)線程直到隊(duì)列不滿拧晕。當(dāng)隊(duì)列為空時(shí)隙姿,消費(fèi)線程會(huì)被阻塞直到隊(duì)列非空。阻塞生產(chǎn)者主要通過(guò) LockSupport 的 park 方法實(shí)現(xiàn)厂捞,不同操作系統(tǒng)中實(shí)現(xiàn)方式不同输玷,在 Linux 下使用的是系統(tǒng)方法 pthread_cond_wait 實(shí)現(xiàn)。

Java 中的阻塞隊(duì)列

ArrayBlockingQueue靡馁,由數(shù)組組成的有界阻塞隊(duì)列欲鹏,默認(rèn)情況下不保證線程公平。

LinkedBlockingQueue臭墨,由鏈表組成的有界阻塞隊(duì)列赔嚎,隊(duì)列的默認(rèn)和最大長(zhǎng)度為 Integer 最大值。

PriorityBlockingQueue胧弛,支持優(yōu)先級(jí)的無(wú)界阻塞隊(duì)列尤误,默認(rèn)情況下元素按升序排序∫镀裕可自定義 compareTo 方法指定排序規(guī)則袄膏,或者初始化時(shí)指定 Comparator 排序,不能保證同優(yōu)先級(jí)元素的順序掺冠。

DelayQueue沉馆,支持延時(shí)獲取元素的無(wú)界阻塞隊(duì)列码党,使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)。創(chuàng)建元素時(shí)可以指定多久才能從隊(duì)列中獲取當(dāng)前元素斥黑,只有延遲期滿時(shí)才能從隊(duì)列中獲取元素揖盘,適用于緩存和定時(shí)調(diào)度。

SynchronousQueue锌奴,不存儲(chǔ)元素的阻塞隊(duì)列兽狭,每一個(gè) put 必須等待一個(gè) take。默認(rèn)使用非公平策略鹿蜀,適用于傳遞性場(chǎng)景箕慧,吞吐量高。

LinkedBlockingDeque茴恰,鏈表組成的雙向阻塞隊(duì)列颠焦,可從隊(duì)列的兩端插入和移出元素,多線程同時(shí)入隊(duì)時(shí)減少了競(jìng)爭(zhēng)往枣。


ThreadLocal

ThreadLoacl 是線程共享變量伐庭,主要用于線程內(nèi)跨類(lèi)、方法傳遞數(shù)據(jù)分冈。ThreadLoacl 有一個(gè)靜態(tài)內(nèi)部類(lèi) ThreadLocalMap圾另,其 Key 是 ThreadLocal 對(duì)象,值是 Entry 對(duì)象雕沉,Entry 中只有一個(gè) Object 類(lèi)的 vaule 值集乔。ThreadLocal 是線程共享的,但 ThreadLocalMap 是每個(gè)線程私有的蘑秽。ThreadLocal 主要有 set饺著、get 和 remove 三個(gè)方法。

set 方法

首先獲取當(dāng)前線程肠牲,然后再獲取當(dāng)前線程對(duì)應(yīng)的 ThreadLocalMap 類(lèi)型的對(duì)象 map。如果 map 存在就直接設(shè)置值靴跛,key 是當(dāng)前的 ThreadLocal 對(duì)象缀雳,value 是傳入的參數(shù)。

如果 map 不存在就通過(guò) createMap 方法為當(dāng)前線程創(chuàng)建一個(gè) ThreadLocalMap 對(duì)象再設(shè)置值梢睛。

get 方法

首先獲取當(dāng)前線程肥印,然后再獲取當(dāng)前線程對(duì)應(yīng)的 ThreadLocalMap 類(lèi)型的對(duì)象 map。如果 map 存在就以當(dāng)前 ThreadLocal 對(duì)象作為 key 獲取 Entry 類(lèi)型的對(duì)象 e绝葡,如果 e 存在就返回它的 value 屬性深碱。

如果 e 不存在或 map 不存在,就調(diào)用 setInitialValue 方法為當(dāng)前線程創(chuàng)建一個(gè) ThreadLocalMap 對(duì)象然后返回默認(rèn)的初始值 null藏畅。

remove 方法

首先通過(guò)當(dāng)前線程獲取其對(duì)應(yīng)的 ThreadLocalMap 類(lèi)型的對(duì)象 m敷硅,如果 m 不為空,就解除 ThreadLocal 這個(gè) key 及其對(duì)應(yīng)的 value 值的聯(lián)系。

存在的問(wèn)題

線程復(fù)用會(huì)產(chǎn)生臟數(shù)據(jù)绞蹦,由于線程池會(huì)重用 Thread 對(duì)象力奋,因此與 Thread 綁定的 ThreadLocal 也會(huì)被重用。如果沒(méi)有調(diào)用 remove 清理與線程相關(guān)的 ThreadLocal 信息幽七,那么假如下一個(gè)線程沒(méi)有調(diào)用 set 設(shè)置初始值就可能 get 到重用的線程信息景殷。

ThreadLocal 還存在內(nèi)存泄漏的問(wèn)題,由于 ThreadLocal 是弱引用澡屡,但 Entry 的 value 是強(qiáng)引用猿挚,因此當(dāng) ThreadLocal 被垃圾回收后,value 依舊不會(huì)被釋放驶鹉。因此需要及時(shí)調(diào)用 remove 方法進(jìn)行清理操作亭饵。


JUC

CAS

CAS 表示比較并交換,需要三個(gè)操作數(shù)梁厉,內(nèi)存位置 V辜羊、舊的預(yù)期值 A 和準(zhǔn)備設(shè)置的新值 B。CAS 執(zhí)行時(shí)词顾,當(dāng)且僅當(dāng) V=A 時(shí)八秃,處理器才會(huì)用 B 更新 V 的值。不管是否更新都會(huì)返回 V 的舊值肉盹,處理過(guò)程是原子操作昔驱,期間不會(huì)被其他線程打斷。

JDK5 開(kāi)始使用 CAS 操作上忍,該操作由 Unsafe 類(lèi)里的 compareAndSwapInt 等幾個(gè)方法提供骤肛。HotSpot 在內(nèi)部對(duì)這些方法做了特殊處理,即時(shí)編譯的結(jié)果是一條平臺(tái)相關(guān)的處理器 CAS 指令窍蓝。Unsafe 類(lèi)不是給用戶程序調(diào)用的類(lèi)腋颠,因此 JDK9 前只有 Java 類(lèi)庫(kù)可以使用 CAS,譬如 AtomicInteger 類(lèi)中 compareAndSet 等方法都使用了 Unsafe 類(lèi)的 CAS 操作實(shí)現(xiàn)吓笙。

CAS 存在一個(gè)漏洞:如果初次讀取和準(zhǔn)備賦值時(shí)都滿足 V=A淑玫,依舊不能說(shuō)明值沒(méi)有被其他線程更改過(guò),因?yàn)榇嬖?V 先改為 B 又改回 A 的情況面睛,這個(gè)漏洞稱為 ABA 問(wèn)題絮蒿。juc 包提供了一個(gè) AtomicStampedReference 類(lèi),通過(guò)控制變量值的版本來(lái)解決 ABA 問(wèn)題叁鉴。


原子類(lèi)

JDK5 提供了 atomic 包土涝,包中的原子操作類(lèi)提供了簡(jiǎn)單高效、線程安全地更新變量的方式幌墓。到 JDK8 該包共有17個(gè)類(lèi)但壮,依據(jù)作用分為四種:原子更新基本類(lèi)型冀泻、原子更新數(shù)組、原子更新引用茵肃、原子更新字段腔长,atomic 包里的類(lèi)基本都使用 Unsafe 實(shí)現(xiàn),Unsafe 只提供三種 CAS 方法:compareAndSwapInt验残、compareAndSwapLongcompareAndSwapObject捞附,例如原子更新 Boolean 是先轉(zhuǎn)成整形再使用 compareAndSwapInt

  • AtomicInteger 原子更新整形您没、 AtomicLong 原子更新長(zhǎng)整型鸟召、AtomicBoolean 原子更新布爾類(lèi)型。

    AtomicInteger 的 getAndIncrement 調(diào)用 Unsafe 類(lèi)的 getAndAddInt 方法以原子方式將當(dāng)前的值加 1氨鹏,該方法調(diào)用 compareAndSwapInt 更新值欧募。

  • AtomicIntegerArray 原子更新整形數(shù)組、 AtomicLongArray 原子更新長(zhǎng)整型數(shù)組仆抵、 AtomicReferenceArray 原子更新引用數(shù)組跟继。

  • AtomicReference 原子更新引用、AtomicMarkableReference 原子更新帶有標(biāo)記的引用镣丑,AtomicStampedReference 原子更新帶有版本號(hào)的引用舔糖,關(guān)聯(lián)一個(gè)整數(shù)值作為版本號(hào),解決 ABA 問(wèn)題莺匠。

  • AtomicIntegerFieldUpdater 原子更新整形字段金吗、 AtomicLongFieldUpdater 原子更新長(zhǎng)整形字段、AtomicReferenceFieldUpdater 原子更新引用類(lèi)型字段趣竣。


CountDownLatch

CountDownLatch 是基于執(zhí)行時(shí)間的同步類(lèi)摇庙,允許一個(gè)或多個(gè)線程等待其他線程完成操作,構(gòu)造方法接收一個(gè) int 參數(shù)作為計(jì)數(shù)器遥缕。每次調(diào)用 countDown 方法時(shí)計(jì)數(shù)器減 1卫袒,await 方法會(huì)阻塞當(dāng)前線程直到計(jì)數(shù)器變?yōu)?0。


CyclicBarrier

循環(huán)屏障是基于同步到達(dá)某個(gè)點(diǎn)的信號(hào)量觸發(fā)機(jī)制通砍,作用是讓一組線程到達(dá)一個(gè)屏障時(shí)被阻塞玛臂,直到最后一個(gè)線程到達(dá)屏障才會(huì)解除。構(gòu)造方法中的參數(shù)表示攔截線程數(shù)量封孙,每個(gè)線程調(diào)用 await 方法表示自己已到達(dá)屏障,然后被阻塞讽营。支持在構(gòu)造方法中傳入一個(gè) Runnable 任務(wù)虎忌,當(dāng)線程到達(dá)屏障時(shí)會(huì)優(yōu)先執(zhí)行該任務(wù)。適用于多線程計(jì)算數(shù)據(jù)橱鹏,最后合并計(jì)算結(jié)果的應(yīng)用場(chǎng)景膜蠢。

CountDownLacth 的計(jì)數(shù)器只能用一次堪藐,而 CyclicBarrier 的計(jì)數(shù)器可用 reset 方法重置,所以 CyclicBarrier 能處理更為復(fù)雜的業(yè)務(wù)挑围。


Semaphore

信號(hào)量用來(lái)控制同時(shí)訪問(wèn)特定資源的線程數(shù)量礁竞,通過(guò)協(xié)調(diào)各個(gè)線程以保證合理使用公共資源。信號(hào)量可以用于流量控制杉辙,特別是公共資源有限的應(yīng)用場(chǎng)景模捂,比如數(shù)據(jù)庫(kù)連接。

Semaphore 的構(gòu)造方法參數(shù)接收一個(gè) int 值蜘矢,表示可用的許可數(shù)量即最大并發(fā)數(shù)狂男。使用 acquire 方法獲得一個(gè)許可證,使用 release 方法歸還許可品腹,還可以用 tryAcquire 嘗試獲得許可岖食。


Exchanger

交換者是用于線程間協(xié)作的工具類(lèi),用于進(jìn)行線程間的數(shù)據(jù)交換舞吭。它提供一個(gè)同步點(diǎn)泡垃,在這個(gè)同步點(diǎn)兩個(gè)線程可以交換彼此的數(shù)據(jù)。

兩個(gè)線程通過(guò) exchange 方法交換數(shù)據(jù)羡鸥,第一個(gè)線程執(zhí)行 exchange 方法后會(huì)阻塞等待第二個(gè)線程執(zhí)行該方法蔑穴,當(dāng)兩個(gè)線程都到達(dá)同步點(diǎn)時(shí)這兩個(gè)線程就可以交換數(shù)據(jù),將本線程生產(chǎn)出的數(shù)據(jù)傳遞給對(duì)方兄春,可用于校對(duì)工作等場(chǎng)景澎剥。


ConcurrentHashMap?

ConcurrentHashMap 用于解決 HashMap 的線程安全和 HashTable 的低效問(wèn)題,HashTable 效率低是因?yàn)樗芯€程都必須競(jìng)爭(zhēng)同一把鎖赶舆,假如容器有多把鎖哑姚,每把鎖只鎖部分?jǐn)?shù)據(jù),那么多線程訪問(wèn)不同數(shù)據(jù)段時(shí)就不會(huì)存在競(jìng)爭(zhēng)芜茵,ConcurrentHashMap 使用鎖分段叙量,將數(shù)據(jù)分成 Segment 數(shù)據(jù)段,給每個(gè)數(shù)據(jù)段配一把鎖九串,當(dāng)一個(gè)線程占用鎖訪問(wèn)某段數(shù)據(jù)時(shí)绞佩,其他數(shù)據(jù)段也能被其他線程訪問(wèn)。

get 實(shí)現(xiàn)簡(jiǎn)單高效猪钮,先經(jīng)過(guò)一次再散列得到一個(gè) hash 值品山,再用這個(gè) hash 值定位到 Segment,最后通過(guò)散列算法定位到元素烤低。get 的高效在于不用加鎖肘交,除非讀到空值才會(huì)加鎖重讀。get 方法中將共享變量定義為 volatile扑馁,由于只需要讀所以不用加鎖涯呻。

put 必須加鎖凉驻,首先定位到 Segment,然后進(jìn)行插入操作复罐,第一步判斷是否需要對(duì) Segment 里的 HashEntry 數(shù)組進(jìn)行擴(kuò)容涝登,第二步定位添加元素的位置,然后將其放入數(shù)組效诅。

size 方法用于統(tǒng)計(jì)數(shù)量胀滚,必須統(tǒng)計(jì)每個(gè) Segment 的大小然后求和,在統(tǒng)計(jì)結(jié)果累加的過(guò)程中填帽,之前累加過(guò)的 count 變化幾率很小蛛淋,因此先嘗試兩次不加鎖統(tǒng)計(jì)結(jié)果,如果統(tǒng)計(jì)過(guò)程中容器大小發(fā)生了變化篡腌,再加鎖統(tǒng)計(jì)褐荷。判斷容器是否發(fā)生變化根據(jù) modCount 確定。

JDK8

主要改造:① 取消分段鎖機(jī)制嘹悼,降低沖突概率叛甫。② 同一個(gè)哈希槽上的元素個(gè)數(shù)超過(guò)閾值后,鏈表改為紅黑樹(shù)結(jié)構(gòu)杨伙。③ 使用優(yōu)化方式統(tǒng)計(jì)元素?cái)?shù)量其监,在涉及元素總數(shù)的更新和計(jì)算時(shí)都避免了鎖,使用 CAS 代替限匣。

get 同樣不需要同步抖苦,put 時(shí)如果沒(méi)有出現(xiàn)哈希沖突,就使用 CAS 添加元素米死,否則使用 synchronized 添加元素锌历。

當(dāng)某個(gè)槽內(nèi)的元素個(gè)數(shù)達(dá)到 7 且 table 容量不小于 64 時(shí),鏈表轉(zhuǎn)為紅黑樹(shù)峦筒。

當(dāng)某個(gè)槽內(nèi)的元素減少到 6 時(shí)究西,由紅黑樹(shù)重新轉(zhuǎn)為鏈表。在轉(zhuǎn)化過(guò)程中物喷,使用同步塊鎖住當(dāng)前槽的首元素卤材,防止其他線程對(duì)當(dāng)前槽進(jìn)行增刪改操作,轉(zhuǎn)化完成后利用 CAS 替換原有鏈表峦失。由于 TreeNode 節(jié)點(diǎn)也存儲(chǔ)了 next 引用扇丛,因此紅黑樹(shù)轉(zhuǎn)為鏈表很簡(jiǎn)單,只需從 first 元素開(kāi)始遍歷所有節(jié)點(diǎn)尉辑,并把節(jié)點(diǎn)從 TreeNode 轉(zhuǎn)為 Node 類(lèi)型即可晕拆,當(dāng)構(gòu)造好新鏈表后同樣用 CAS 替換紅黑樹(shù)。


CopyOnWriteArrayList

可以使用 CopyOnWriteArrayList 代替 ArrayList材蹬,它實(shí)現(xiàn)了讀寫(xiě)分離实幕。寫(xiě)操作復(fù)制一個(gè)新的集合,在新集合內(nèi)添加或刪除元素堤器,修改完成后再將原集合的引用指向新集合昆庇。這樣做的好處是可以高并發(fā)地進(jìn)行讀寫(xiě)操作而不需要加鎖,因?yàn)楫?dāng)前集合不會(huì)添加任何元素闸溃。使用時(shí)注意盡量設(shè)置容量初始值整吆,使用批量添加或刪除,避免多次擴(kuò)容辉川,防止只增加一個(gè)元素卻復(fù)制整個(gè)集合表蝙。

適合讀多寫(xiě)少的場(chǎng)景,單個(gè)添加時(shí)效率極低乓旗。CopyOnWriteArrayList 是 fail-safe 的府蛇,并發(fā)包的集合都是這種機(jī)制,fail-safe 在安全的副本上遍歷屿愚,集合修改與副本遍歷沒(méi)有任何關(guān)系汇跨,缺點(diǎn)是無(wú)法讀取最新數(shù)據(jù)。這也是 CAP 理論中 C(一致性) 和 A(可用性) 的矛盾妆距。


參考資料

[1] HashMap和Hashtable的區(qū)別
[2] # JAVA基礎(chǔ)知識(shí)之IO——Java IO體系及常用類(lèi)
[3] Java反射介紹
[4] Java高級(jí)特性——反射
[5] # JAVA多線程之線程間的通信方式
[6] HashMap解決沖突的四種方法
[7] # hashmap沖突的解決方法以及原理分析:
[8] # Java多線程看這一篇就足夠了(吐血超詳細(xì)總結(jié))
[9] Java多線程超詳解很詳細(xì)
[10] CPU內(nèi)存模型和Java內(nèi)存模型以及Java內(nèi)存區(qū)域
[11] volatile關(guān)鍵字的作用以及原理
[12] # JVM的內(nèi)存管理機(jī)制 特別好
[13] java中抽象類(lèi)和接口有什么區(qū)別
[14] 漫畫(huà):什么是ConcurrentHashMap穷遂?
[15] ConcurrentHashMap底層實(shí)現(xiàn)原理(JDK1.7 & 1.8)
[16] Concurrenthashmap的實(shí)現(xiàn)原理分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市娱据,隨后出現(xiàn)的幾起案子蚪黑,更是在濱河造成了極大的恐慌,老刑警劉巖中剩,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忌穿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡咽安,警方通過(guò)查閱死者的電腦和手機(jī)伴网,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)妆棒,“玉大人澡腾,你說(shuō)我怎么就攤上這事「馍海” “怎么了动分?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)红选。 經(jīng)常有香客問(wèn)我戒祠,道長(zhǎng),這世上最難降的妖魔是什么撩轰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮迹辐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘甚侣。我一直安慰自己明吩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布殷费。 她就那樣靜靜地躺著印荔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪详羡。 梳的紋絲不亂的頭發(fā)上仍律,一...
    開(kāi)封第一講書(shū)人閱讀 48,954評(píng)論 1 283
  • 那天,我揣著相機(jī)與錄音实柠,去河邊找鬼水泉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛主到,可吹牛的內(nèi)容都是我干的茶行。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼登钥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼畔师!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起牧牢,我...
    開(kāi)封第一講書(shū)人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤看锉,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后塔鳍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體伯铣,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年轮纫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腔寡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掌唾,死狀恐怖放前,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情糯彬,我是刑警寧澤凭语,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站撩扒,受9級(jí)特大地震影響似扔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一炒辉、第九天 我趴在偏房一處隱蔽的房頂上張望豪墅。 院中可真熱鬧,春花似錦辆脸、人聲如沸但校。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至术裸,卻和暖如春倘是,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袭艺。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工搀崭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人猾编。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓瘤睹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親答倡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子轰传,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345