寫(xiě)在前面
最近蕾久,一直有小伙伴讓我整理下關(guān)于JVM的知識(shí),經(jīng)過(guò)十幾天的收集與整理拌夏,初版算是整理出來(lái)了僧著。希望對(duì)大家有所幫助。
JDK 是什么障簿?
JDK 是用于支持 Java 程序開(kāi)發(fā)的最小環(huán)境盹愚。
Java 程序設(shè)計(jì)語(yǔ)言
Java 虛擬機(jī)
Java API類(lèi)庫(kù)
JRE 是什么?
JRE 是支持 Java 程序運(yùn)行的標(biāo)準(zhǔn)環(huán)境卷谈。
Java SE API 子集
Java 虛擬機(jī)
Java歷史版本的特性?
Java Version SE 5.0
引入泛型霞篡;
增強(qiáng)循環(huán)世蔗,可以使用迭代方式端逼;
自動(dòng)裝箱與自動(dòng)拆箱;
類(lèi)型安全的枚舉污淋;
可變參數(shù)顶滩;
靜態(tài)引入;
元數(shù)據(jù)(注解)寸爆;
引入Instrumentation礁鲁。
Java Version SE 6
支持腳本語(yǔ)言;
引入JDBC 4.0 API赁豆;
引入Java Compiler API仅醇;
可插拔注解;
增加對(duì)Native PKI(Public Key Infrastructure)魔种、Java GSS(Generic Security Service)析二、Kerberos和LDAP(Lightweight Directory Access Protocol)的支持;
繼承Web Services节预;
做了很多優(yōu)化叶摄。
Java Version SE 7
switch語(yǔ)句塊中允許以字符串作為分支條件;
在創(chuàng)建泛型對(duì)象時(shí)應(yīng)用類(lèi)型推斷安拟;
在一個(gè)語(yǔ)句塊中捕獲多種異常蛤吓;
支持動(dòng)態(tài)語(yǔ)言;
支持try-with-resources糠赦;
引入Java NIO.2開(kāi)發(fā)包会傲;
數(shù)值類(lèi)型可以用2進(jìn)制字符串表示,并且可以在字符串表示中添加下劃線愉棱;
鉆石型語(yǔ)法唆铐;
null值的自動(dòng)處理。
Java 8
函數(shù)式接口
Lambda表達(dá)式
Stream API
接口的增強(qiáng)
時(shí)間日期增強(qiáng)API
重復(fù)注解與類(lèi)型注解
默認(rèn)方法與靜態(tài)方法
Optional 容器類(lèi)
運(yùn)行時(shí)數(shù)據(jù)區(qū)域包括哪些奔滑?
程序計(jì)數(shù)器
Java 虛擬機(jī)棧
本地方法棧
Java 堆
方法區(qū)
運(yùn)行時(shí)常量池
直接內(nèi)存
程序計(jì)數(shù)器(線程私有)
程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間嚎京,可以看作是當(dāng)前線程所執(zhí)行字節(jié)碼的行號(hào)指示器。分支桅咆、循環(huán)斗遏、跳轉(zhuǎn)、異常處理梅猿、線程恢復(fù)等基礎(chǔ)功能都需要依賴(lài)這個(gè)計(jì)數(shù)器完成氓辣。
由于 Java 虛擬機(jī)的多線程是通過(guò)線程輪流切換并分配處理器執(zhí)行時(shí)間的方式實(shí)現(xiàn)的。為了線程切換后能恢復(fù)到正確的執(zhí)行位置袱蚓,每條線程都需要一個(gè)獨(dú)立的程序計(jì)數(shù)器钞啸,各線程之間的計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ)。
如果線程正在執(zhí)行的是一個(gè) Java 方法体斩,計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址梭稚;
如果正在執(zhí)行的是 Native 方法,這個(gè)計(jì)數(shù)器的值為空絮吵。
程序計(jì)數(shù)器是唯一一個(gè)沒(méi)有規(guī)定任何 OutOfMemoryError 的區(qū)域弧烤。
Java 虛擬機(jī)棧(線程私有)
Java 虛擬機(jī)棧(Java Virtual Machine Stacks)是線程私有的,生命周期與線程相同蹬敲。 虛擬機(jī)棧描述的是 Java 方法執(zhí)行的內(nèi)存模型:每個(gè)方法被執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)暇昂,存儲(chǔ)
局部變量表
操作棧
動(dòng)態(tài)鏈接
方法出口
每一個(gè)方法被調(diào)用到執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程伴嗡。
這個(gè)區(qū)域有兩種異常情況:
StackOverflowError:線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度
OutOfMemoryError:虛擬機(jī)棧擴(kuò)展到無(wú)法申請(qǐng)足夠的內(nèi)存時(shí)
本地方法棧(線程私有)
虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法(字節(jié)碼)服務(wù)急波。
本地方法棧(Native Method Stacks)為虛擬機(jī)使用到的 Native 方法服務(wù)。
Java 堆(線程共享)
Java 堆(Java Heap)是 Java 虛擬機(jī)中內(nèi)存最大的一塊闹究。Java 堆在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建幔崖,被所有線程共享。
作用:存放對(duì)象實(shí)例渣淤。垃圾收集器主要管理的就是 Java 堆赏寇。Java 堆在物理上可以不連續(xù),只要邏輯上連續(xù)即可价认。
方法區(qū)(線程共享)
方法區(qū)(Method Area)被所有線程共享嗅定,用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息、常量用踩、靜態(tài)變量渠退、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
和 Java 堆一樣脐彩,不需要連續(xù)的內(nèi)存碎乃,可以選擇固定的大小,更可以選擇不實(shí)現(xiàn)垃圾收集惠奸。
運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分梅誓。保存 Class 文件中的符號(hào)引用、翻譯出來(lái)的直接引用佛南。運(yùn)行時(shí)常量池可以在運(yùn)行期間將新的常量放入池中梗掰。
Java 中對(duì)象訪問(wèn)是如何進(jìn)行的?
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" cid="n149" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" lang="java">Object obj = new Object();</pre>
對(duì)于上述最簡(jiǎn)單的訪問(wèn)嗅回,也會(huì)涉及到 Java 棧及穗、Java 堆、方法區(qū)這三個(gè)最重要內(nèi)存區(qū)域绵载。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" cid="n151" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" lang="java">Object obj</pre>
如果出現(xiàn)在方法體中埂陆,則上述代碼會(huì)反映到 Java 棧的本地變量表中苛白,作為 reference 類(lèi)型數(shù)據(jù)出現(xiàn)。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" cid="n153" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" lang="java">new Object()</pre>
反映到 Java 堆中焚虱,形成一塊存儲(chǔ)了 Object 類(lèi)型所有對(duì)象實(shí)例數(shù)據(jù)值的內(nèi)存丸氛。Java堆中還包含對(duì)象類(lèi)型數(shù)據(jù)的地址信息,這些類(lèi)型數(shù)據(jù)存儲(chǔ)在方法區(qū)中著摔。
如何判斷對(duì)象是否“死去”?
引用計(jì)數(shù)法
根搜索算法
什么是引用計(jì)數(shù)法定续?
給對(duì)象添加一個(gè)引用計(jì)數(shù)器谍咆,每當(dāng)有一個(gè)地方引用它,計(jì)數(shù)器就+1,私股;當(dāng)引用失效時(shí)摹察,計(jì)數(shù)器就-1;任何時(shí)刻計(jì)數(shù)器都為0的對(duì)象就是不能再被使用的倡鲸。
引用計(jì)數(shù)法的缺點(diǎn)供嚎?
很難解決對(duì)象之間的循環(huán)引用問(wèn)題。
什么是根搜索算法峭状?
通過(guò)一系列的名為“GC Roots”的對(duì)象作為起始點(diǎn)克滴,從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索所走過(guò)的路徑稱(chēng)為引用鏈(Reference Chain)优床,當(dāng)一個(gè)對(duì)象到 GC Roots 沒(méi)有任何引用鏈相連(用圖論的話來(lái)說(shuō)就是從 GC Roots 到這個(gè)對(duì)象不可達(dá))時(shí)劝赔,則證明此對(duì)象是不可用的。
[圖片上傳失敗...(image-db1fcc-1605629836840)]
Java 的4種引用方式胆敞?
在 JDK 1.2 之后着帽,Java 對(duì)引用的概念進(jìn)行了擴(kuò)充,將引用分為
強(qiáng)引用 Strong Reference
軟引用 Soft Reference
弱引用 Weak Reference
虛引用 Phantom Reference
強(qiáng)引用
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" cid="n180" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" lang="java">Object obj = new Object();</pre>
代碼中普遍存在的移层,像上述的引用仍翰。只要強(qiáng)引用還在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象观话。
軟引用
用來(lái)描述一些還有用予借,但并非必須的對(duì)象。軟引用所關(guān)聯(lián)的對(duì)象匪燕,有在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前蕾羊,將會(huì)把這些對(duì)象列進(jìn)回收范圍,并進(jìn)行第二次回收帽驯。如果這次回收還是沒(méi)有足夠的內(nèi)存龟再,才會(huì)拋出內(nèi)存異常。提供了 SoftReference 類(lèi)實(shí)現(xiàn)軟引用尼变。
弱引用
描述非必須的對(duì)象利凑,強(qiáng)度比軟引用更弱一些浆劲,被弱引用關(guān)聯(lián)的對(duì)象,只能生存到下一次垃圾收集發(fā)生前哀澈。當(dāng)垃圾收集器工作時(shí)牌借,無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象割按。提供了 WeakReference 類(lèi)來(lái)實(shí)現(xiàn)弱引用膨报。
虛引用
一個(gè)對(duì)象是否有虛引用,完全不會(huì)對(duì)其生存時(shí)間夠成影響适荣,也無(wú)法通過(guò)虛引用來(lái)取得一個(gè)對(duì)象實(shí)例现柠。為一個(gè)對(duì)象關(guān)聯(lián)虛引用的唯一目的,就是希望在這個(gè)對(duì)象被收集器回收時(shí)弛矛,收到一個(gè)系統(tǒng)通知够吩。提供了 PhantomReference 類(lèi)來(lái)實(shí)現(xiàn)虛引用。
有哪些垃圾收集算法丈氓?
標(biāo)記-清除算法
復(fù)制算法
標(biāo)記-整理算法
分代收集算法
標(biāo)記-清除算法(Mark-Sweep)
什么是標(biāo)記-清除算法周循?
分為標(biāo)記和清除兩個(gè)階段。首先標(biāo)記出所有需要回收的對(duì)象万俗,在標(biāo)記完成后統(tǒng)一回收被標(biāo)記的對(duì)象湾笛。
有什么缺點(diǎn)?
效率問(wèn)題:標(biāo)記和清除過(guò)程的效率都不高闰歪。
空間問(wèn)題:標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片迄本,空間碎片太多可能導(dǎo)致,程序分配較大對(duì)象時(shí)無(wú)法找到足夠的連續(xù)內(nèi)存课竣,不得不提前出發(fā)另一次垃圾收集動(dòng)作嘉赎。
[圖片上傳失敗...(image-45c00-1605629836840)]
復(fù)制算法(Copying)- 新生代
將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中一塊于樟。當(dāng)這一塊的內(nèi)存用完了公条,就將存活著的對(duì)象復(fù)制到另一塊上面,然后再把已經(jīng)使用過(guò)的內(nèi)存空間一次清理掉迂曲。
優(yōu)點(diǎn)
復(fù)制算法使得每次都是針對(duì)其中的一塊進(jìn)行內(nèi)存回收靶橱,內(nèi)存分配時(shí)也不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動(dòng)堆頂指針路捧,按順序分配內(nèi)存即可关霸,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效杰扫。
缺點(diǎn)
將內(nèi)存縮小為原來(lái)的一半队寇。在對(duì)象存活率較高時(shí),需要執(zhí)行較多的復(fù)制操作章姓,效率會(huì)變低佳遣。
[圖片上傳失敗...(image-98d6fd-1605629836840)]
應(yīng)用
商業(yè)的虛擬機(jī)都采用復(fù)制算法來(lái)回收新生代识埋。因?yàn)樾律械膶?duì)象容易死亡,所以并不需要按照1:1的比例劃分內(nèi)存空間零渐,而是將內(nèi)存分為一塊較大的 Eden 空間和兩塊較小的 Survivor 空間窒舟。每次使用 Eden 和其中的一塊 Survivor。
當(dāng)回收時(shí)诵盼,將 Eden 和 Survivor 中還存活的對(duì)象一次性拷貝到另外一塊 Survivor 空間上惠豺,最后清理掉 Eden 和剛才用過(guò)的 Survivor 空間。Hotspot 虛擬機(jī)默認(rèn) Eden 和 Survivor 的大小比例是8:1风宁,也就是每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的90%(80% + 10%)耕腾,只有10%的內(nèi)存是會(huì)被“浪費(fèi)”的。
標(biāo)記-整理算法(Mark-Compact)-老年代
標(biāo)記過(guò)程仍然與“標(biāo)記-清除”算法一樣杀糯,但不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象向一端移動(dòng)固翰,然后直接清理掉邊界以外的內(nèi)存。
[圖片上傳失敗...(image-668020-1605629836840)]
分代收集算法
根據(jù)對(duì)象的存活周期歉铝,將內(nèi)存劃分為幾塊。一般是把 Java 堆分為新生代和老年代类缤,這樣就可以根據(jù)各個(gè)年代的特點(diǎn),采用最適當(dāng)?shù)氖占惴ā?/p>
新生代:每次垃圾收集時(shí)會(huì)有大批對(duì)象死去膏蚓,只有少量存活驮瞧,所以選擇復(fù)制算法幢尚,只需要少量存活對(duì)象的復(fù)制成本就可以完成收集。
老年代:對(duì)象存活率高理茎、沒(méi)有額外空間對(duì)它進(jìn)行分配擔(dān)保,必須使用“標(biāo)記-清理”或“標(biāo)記-整理”算法進(jìn)行回收础倍。
Minor GC 和 Full GC有什么區(qū)別沟启?
Minor GC:新生代 GC德迹,指發(fā)生在新生代的垃圾收集動(dòng)作,因?yàn)?Java 對(duì)象大多死亡頻繁肌毅,所以 Minor GC 非常頻繁,一般回收速度較快。 Full GC:老年代 GC艰躺,也叫 Major GC,速度一般比 Minor GC 慢 10 倍以上页响。
Java 內(nèi)存
為什么要將堆內(nèi)存分區(qū)栈拖?
對(duì)于一個(gè)大型的系統(tǒng),當(dāng)創(chuàng)建的對(duì)象及方法變量比較多時(shí)贴彼,即堆內(nèi)存中的對(duì)象比較多,如果逐一分析對(duì)象是否該回收精钮,效率很低专普。分區(qū)是為了進(jìn)行模塊化管理筋粗,管理不同的對(duì)象及變量,以提高 JVM 的執(zhí)行效率。
堆內(nèi)存分為哪幾塊?
Young Generation Space 新生區(qū)(也稱(chēng)新生代)
Tenure Generation Space養(yǎng)老區(qū)(也稱(chēng)舊生代)
Permanent Space 永久存儲(chǔ)區(qū)
分代收集算法
內(nèi)存分配有哪些原則躲舌?
對(duì)象優(yōu)先分配在 Eden
大對(duì)象直接進(jìn)入老年代
長(zhǎng)期存活的對(duì)象將進(jìn)入老年代
動(dòng)態(tài)對(duì)象年齡判定
空間分配擔(dān)保
Young Generation Space (采用復(fù)制算法)
主要用來(lái)存儲(chǔ)新創(chuàng)建的對(duì)象,內(nèi)存較小诀拭,垃圾回收頻繁。這個(gè)區(qū)又分為三個(gè)區(qū)域:一個(gè) Eden Space 和兩個(gè) Survivor Space俗孝。
當(dāng)對(duì)象在堆創(chuàng)建時(shí),將進(jìn)入年輕代的Eden Space。
垃圾回收器進(jìn)行垃圾回收時(shí),掃描Eden Space和A Suvivor Space筑凫,如果對(duì)象仍然存活巍实,則復(fù)制到B Suvivor Space膝昆,如果B Suvivor Space已經(jīng)滿(mǎn)原环,則復(fù)制 Old Gen
掃描A Suvivor Space時(shí),如果對(duì)象已經(jīng)經(jīng)過(guò)了幾次的掃描仍然存活,JVM認(rèn)為其為一個(gè)Old對(duì)象患膛,則將其移到Old Gen踪蹬。
掃描完畢后臣咖,JVM將Eden Space和A Suvivor Space清空,然后交換A和B的角色(即下次垃圾回收時(shí)會(huì)掃描Eden Space和B Suvivor Space甚脉。
Tenure Generation Space(采用標(biāo)記-整理算法)
主要用來(lái)存儲(chǔ)長(zhǎng)時(shí)間被引用的對(duì)象。它里面存放的是經(jīng)過(guò)幾次在 Young Generation Space 進(jìn)行掃描判斷過(guò)仍存活的對(duì)象涛碑,內(nèi)存較大,垃圾回收頻率較小。
Permanent Space
存儲(chǔ)不變的類(lèi)定義、字節(jié)碼和常量等。
Class文件
Java虛擬機(jī)的平臺(tái)無(wú)關(guān)性
[圖片上傳失敗...(image-d02385-1605629836839)]
Class文件的組成?
Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進(jìn)制流然爆,各個(gè)數(shù)據(jù)項(xiàng)目間沒(méi)有任何分隔符翻默。當(dāng)遇到8位字節(jié)以上空間的數(shù)據(jù)項(xiàng)時(shí),則會(huì)按照高位在前的方式分隔成若干個(gè)8位字節(jié)進(jìn)行存儲(chǔ)蹦渣。
魔數(shù)與Class文件的版本
每個(gè)Class文件的頭4個(gè)字節(jié)稱(chēng)為魔數(shù)(Magic Number)锄奢,它的唯一作用是用于確定這個(gè)文件是否為一個(gè)能被虛擬機(jī)接受的Class文件灰伟。OxCAFEBABE义矛。
接下來(lái)是Class文件的版本號(hào):第5,6字節(jié)是次版本號(hào)(Minor Version),第7,8字節(jié)是主版本號(hào)(Major Version)。
使用JDK 1.7編譯輸出Class文件旺垒,格式代碼為:
[圖片上傳失敗...(image-1a6f3d-1605629836839)]
前四個(gè)字節(jié)為魔數(shù)竞漾,次版本號(hào)是0x0000,主版本號(hào)是0x0033化戳,說(shuō)明本文件是可以被1.7及以上版本的虛擬機(jī)執(zhí)行的文件。
33:JDK1.7
32:JDK1.6
31:JDK1.5
30:JDK1.4
2F:JDK1.3
[圖片上傳失敗...(image-3098ba-1605629836839)]
類(lèi)加載器
類(lèi)加載器的作用是什么?
類(lèi)加載器實(shí)現(xiàn)類(lèi)的加載動(dòng)作吃嘿,同時(shí)用于確定一個(gè)類(lèi)嘱支。對(duì)于任意一個(gè)類(lèi),都需要由加載它的類(lèi)加載器和這個(gè)類(lèi)本身一同確立其在Java虛擬機(jī)中的唯一性搓侄。即使兩個(gè)類(lèi)來(lái)源于同一個(gè)Class文件,只要加載它們的類(lèi)加載器不同汹忠,這兩個(gè)類(lèi)就不相等烈菌。
類(lèi)加載器有哪些?
啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader):使用C++實(shí)現(xiàn)(僅限于HotSpot)宠漩,是虛擬機(jī)自身的一部分融撞。負(fù)責(zé)將存放在\lib目錄中的類(lèi)庫(kù)加載到虛擬機(jī)中。其無(wú)法被Java程序直接引用裆针。
擴(kuò)展類(lèi)加載器(Extention ClassLoader)由ExtClassLoader實(shí)現(xiàn)沐祷,負(fù)責(zé)加載\lib\ext目錄中的所有類(lèi)庫(kù),開(kāi)發(fā)者可以直接使用帽蝶。
應(yīng)用程序類(lèi)加載器(Application ClassLoader):由APPClassLoader實(shí)現(xiàn)囱井。負(fù)責(zé)加載用戶(hù)類(lèi)路徑(ClassPath)上所指定的類(lèi)庫(kù)麦锯。
類(lèi)加載機(jī)制
什么是雙親委派模型?
雙親委派模型(Parents Delegation Model)要求除了頂層的啟動(dòng)類(lèi)加載器外琅绅,其余加載器都應(yīng)當(dāng)有自己的父類(lèi)加載器扶欣。類(lèi)加載器之間的父子關(guān)系,通過(guò)組合關(guān)系復(fù)用千扶。 工作過(guò)程:如果一個(gè)類(lèi)加載器收到了類(lèi)加載的請(qǐng)求料祠,它首先不會(huì)自己去嘗試加載這個(gè)類(lèi),而是把這個(gè)請(qǐng)求委派給父類(lèi)加載器完成澎羞。每個(gè)層次的類(lèi)加載器都是如此髓绽,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類(lèi)加載器中,只有到父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求(它的搜索范圍沒(méi)有找到所需的類(lèi))時(shí)妆绞,子加載器才會(huì)嘗試自己去加載顺呕。
為什么要使用雙親委派模型枫攀,組織類(lèi)加載器之間的關(guān)系?
Java類(lèi)隨著它的類(lèi)加載器一起具備了一種帶優(yōu)先級(jí)的層次關(guān)系株茶。比如java.lang.Object来涨,它存放在rt.jar中,無(wú)論哪個(gè)類(lèi)加載器要加載這個(gè)類(lèi)启盛,最終都是委派給啟動(dòng)類(lèi)加載器進(jìn)行加載蹦掐,因此Object類(lèi)在程序的各個(gè)類(lèi)加載器環(huán)境中,都是同一個(gè)類(lèi)僵闯。
如果沒(méi)有使用雙親委派模型卧抗,讓各個(gè)類(lèi)加載器自己去加載,那么Java類(lèi)型體系中最基礎(chǔ)的行為也得不到保障鳖粟,應(yīng)用程序會(huì)變得一片混亂社裆。
[圖片上傳失敗...(image-2f7fdc-1605629836839)]
什么是類(lèi)加載機(jī)制?
Class文件描述的各種信息向图,都需要加載到虛擬機(jī)后才能運(yùn)行浦马。虛擬機(jī)把描述類(lèi)的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)张漂、轉(zhuǎn)換解析和初始化晶默,最終形成可以被虛擬機(jī)直接使用的Java類(lèi)型,這就是虛擬機(jī)的類(lèi)加載機(jī)制航攒。
虛擬機(jī)和物理機(jī)的區(qū)別是什么磺陡?
這兩種機(jī)器都有代碼執(zhí)行的能力,但是:
物理機(jī)的執(zhí)行引擎是直接建立在處理器漠畜、硬件币他、指令集和操作系統(tǒng)層面的。
虛擬機(jī)的執(zhí)行引擎是自己實(shí)現(xiàn)的憔狞,因此可以自行制定指令集和執(zhí)行引擎的結(jié)構(gòu)體系蝴悉,并且能夠執(zhí)行那些不被硬件直接支持的指令集格式。
運(yùn)行時(shí)棧幀結(jié)構(gòu)
棧幀是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)瘾敢, 存儲(chǔ)了方法的
局部變量表
操作數(shù)棧
動(dòng)態(tài)連接
方法返回地址
每一個(gè)方法從調(diào)用開(kāi)始到執(zhí)行完成的過(guò)程拍冠,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧里面從入棧到出棧的過(guò)程。
[圖片上傳失敗...(image-7cb907-1605629836839)]
Java 方法調(diào)用
什么是方法調(diào)用簇抵?
方法調(diào)用唯一的任務(wù)是確定被調(diào)用方法的版本(調(diào)用哪個(gè)方法)庆杜,暫時(shí)還不涉及方法內(nèi)部的具體運(yùn)行過(guò)程。
Java的方法調(diào)用碟摆,有什么特殊之處晃财?
Class文件的編譯過(guò)程不包含傳統(tǒng)編譯的連接步驟,一切方法調(diào)用在Class文件里面存儲(chǔ)的都只是符號(hào)引用典蜕,而不是方法在實(shí)際運(yùn)行時(shí)內(nèi)存布局中的入口地址断盛。這使得Java有強(qiáng)大的動(dòng)態(tài)擴(kuò)展能力罗洗,但使Java方法的調(diào)用過(guò)程變得相對(duì)復(fù)雜,需要在類(lèi)加載期間甚至到運(yùn)行時(shí)才能確定目標(biāo)方法的直接引用钢猛。
Java虛擬機(jī)調(diào)用字節(jié)碼指令有哪些伙菜?
invokestatic:調(diào)用靜態(tài)方法
invokespecial:調(diào)用實(shí)例構(gòu)造器方法、私有方法和父類(lèi)方法
invokevirtual:調(diào)用所有的虛方法
invokeinterface:調(diào)用接口方法
虛擬機(jī)是如何執(zhí)行方法里面的字節(jié)碼指令的厢洞?
解釋執(zhí)行(通過(guò)解釋器執(zhí)行) 編譯執(zhí)行(通過(guò)即時(shí)編譯器產(chǎn)生本地代碼)
解釋執(zhí)行
當(dāng)主流的虛擬機(jī)中都包含了即時(shí)編譯器后仇让,Class文件中的代碼到底會(huì)被解釋執(zhí)行還是編譯執(zhí)行典奉,只有虛擬機(jī)自己才能準(zhǔn)確判斷躺翻。
Javac編譯器完成了程序代碼經(jīng)過(guò)詞法分析、語(yǔ)法分析到抽象語(yǔ)法樹(shù)卫玖,再遍歷語(yǔ)法樹(shù)生成線性的字節(jié)碼指令流的過(guò)程公你。因?yàn)檫@一動(dòng)作是在Java虛擬機(jī)之外進(jìn)行的,而解釋器在虛擬機(jī)的內(nèi)部假瞬,所以Java程序的編譯是半獨(dú)立的實(shí)現(xiàn)陕靠。
基于棧的指令集和基于寄存器的指令集
什么是基于棧的指令集?
Java編譯器輸出的指令流脱茉,里面的指令大部分都是零地址指令剪芥,它們依賴(lài)操作數(shù)棧進(jìn)行工作。
計(jì)算“1+1=2”琴许,基于棧的指令集是這樣的:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" cid="n353" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" lang="java">iconst_1
iconst_1
iadd
istore_0</pre>
兩條iconst_1指令連續(xù)地把兩個(gè)常量1壓入棧中税肪,iadd指令把棧頂?shù)膬蓚€(gè)值出棧相加,把結(jié)果放回棧頂榜田,最后istore_0把棧頂?shù)闹捣诺骄植孔兞勘淼牡?個(gè)Slot中益兄。
什么是基于寄存器的指令集?
最典型的是x86的地址指令集箭券,依賴(lài)寄存器工作净捅。 計(jì)算“1+1=2”,基于寄存器的指令集是這樣的:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" cid="n357" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" lang="java">mov eax, 1
add eax, 1</pre>
mov指令把EAX寄存器的值設(shè)為1辩块,然后add指令再把這個(gè)值加1蛔六,結(jié)果就保存在EAX寄存器里。
基于棧的指令集的優(yōu)缺點(diǎn)废亭?
優(yōu)點(diǎn):
可移植性好:用戶(hù)程序不會(huì)直接用到這些寄存器古今,由虛擬機(jī)自行決定把一些訪問(wèn)最頻繁的數(shù)據(jù)(程序計(jì)數(shù)器、棧頂緩存)放到寄存器以獲取更好的性能滔以。
代碼相對(duì)緊湊:字節(jié)碼中每個(gè)字節(jié)就對(duì)應(yīng)一條指令
編譯器實(shí)現(xiàn)簡(jiǎn)單:不需要考慮空間分配問(wèn)題捉腥,所需空間都在棧上操作
缺點(diǎn):
執(zhí)行速度稍慢
完成相同功能所需的指令熟練多
頻繁的訪問(wèn)棧,意味著頻繁的訪問(wèn)內(nèi)存你画,相對(duì)于處理器抵碟,內(nèi)存才是執(zhí)行速度的瓶頸桃漾。
Javac編譯過(guò)程分為哪些步驟?
解析與填充符號(hào)表
插入式注解處理器的注解處理
分析與字節(jié)碼生成
[圖片上傳失敗...(image-8de0df-1605629836838)]
什么是即時(shí)編譯器拟逮?
Java程序最初是通過(guò)解釋器進(jìn)行解釋執(zhí)行的撬统,當(dāng)虛擬機(jī)發(fā)現(xiàn)某個(gè)方法或代碼塊的運(yùn)行特別頻繁,就會(huì)把這些代碼認(rèn)定為“熱點(diǎn)代碼”(Hot Spot Code)敦迄。
為了提高熱點(diǎn)代碼的執(zhí)行效率恋追,在運(yùn)行時(shí),虛擬機(jī)將會(huì)把這些代碼編譯成與本地平臺(tái)相關(guān)的機(jī)器碼罚屋,并進(jìn)行各種層次的優(yōu)化苦囱,完成這個(gè)任務(wù)的編譯器成為即時(shí)編譯器(Just In Time Compiler,JIT編譯器)脾猛。
解釋器和編譯器
許多主流的商用虛擬機(jī)撕彤,都同時(shí)包含解釋器和編譯器。
當(dāng)程序需要快速啟動(dòng)和執(zhí)行時(shí)猛拴,解釋器首先發(fā)揮作用羹铅,省去編譯的時(shí)間,立即執(zhí)行愉昆。
當(dāng)程序運(yùn)行后职员,隨著時(shí)間的推移,編譯器逐漸發(fā)揮作用跛溉,把越來(lái)越多的代碼編譯成本地代碼焊切,可以提高執(zhí)行效率。
如果內(nèi)存資源限制較大(部分嵌入式系統(tǒng))倒谷,可以使用解釋執(zhí)行節(jié)約內(nèi)存蛛蒙,反之可以使用編譯執(zhí)行來(lái)提升效率。同時(shí)編譯器的代碼還能退回成解釋器的代碼渤愁。
[圖片上傳失敗...(image-604426-1605629836838)]
為什么要采用分層編譯牵祟?
因?yàn)榧磿r(shí)編譯器編譯本地代碼需要占用程序運(yùn)行時(shí)間,要編譯出優(yōu)化程度更高的代碼抖格,所花費(fèi)的時(shí)間越長(zhǎng)诺苹。
分層編譯器有哪些層次?
分層編譯根據(jù)編譯器編譯雹拄、優(yōu)化的規(guī)模和耗時(shí)收奔,劃分不同的編譯層次,包括:
第0層:程序解釋執(zhí)行滓玖,解釋器不開(kāi)啟性能監(jiān)控功能坪哄,可出發(fā)第1層編譯。
第1層:也成為C1編譯,將字節(jié)碼編譯為本地代碼翩肌,進(jìn)行簡(jiǎn)單可靠的優(yōu)化模暗,如有必要加入性能監(jiān)控的邏輯。
第2層:也成為C2編譯念祭,也是將字節(jié)碼編譯為本地代碼兑宇,但是會(huì)啟用一些編譯耗時(shí)較長(zhǎng)的優(yōu)化,甚至?xí)鶕?jù)性能監(jiān)控信息進(jìn)行一些不可靠的激進(jìn)優(yōu)化粱坤。
用Client Compiler和Server Compiler將會(huì)同時(shí)工作隶糕。用Client Compiler獲取更高的編譯速度,用Server Compiler獲取更好的編譯質(zhì)量站玄。
編譯對(duì)象與觸發(fā)條件
熱點(diǎn)代碼有哪些枚驻?
被多次調(diào)用的方法
被多次執(zhí)行的循環(huán)體
如何判斷一段代碼是不是熱點(diǎn)代碼?
要知道一段代碼是不是熱點(diǎn)代碼蜒什,是不是需要觸發(fā)即時(shí)編譯测秸,這個(gè)行為稱(chēng)為熱點(diǎn)探測(cè)疤估。主要有兩種方法:
基于采樣的熱點(diǎn)探測(cè)灾常,虛擬機(jī)周期性檢查各個(gè)線程的棧頂,如果發(fā)現(xiàn)某個(gè)方法經(jīng)常出現(xiàn)在棧頂铃拇,那這個(gè)方法就是“熱點(diǎn)方法”钞瀑。實(shí)現(xiàn)簡(jiǎn)單高效,但是很難精確確認(rèn)一個(gè)方法的熱度慷荔。
基于計(jì)數(shù)器的熱點(diǎn)探測(cè)雕什,虛擬機(jī)會(huì)為每個(gè)方法建立計(jì)數(shù)器,統(tǒng)計(jì)方法的執(zhí)行次數(shù)显晶,如果執(zhí)行次數(shù)超過(guò)一定的閾值贷岸,就認(rèn)為它是熱點(diǎn)方法。
HotSpot虛擬機(jī)使用第二種磷雇,有兩個(gè)計(jì)數(shù)器:
方法調(diào)用計(jì)數(shù)器
回邊計(jì)數(shù)器(判斷循環(huán)代碼)
方法調(diào)用計(jì)數(shù)器統(tǒng)計(jì)方法
統(tǒng)計(jì)的是一個(gè)相對(duì)的執(zhí)行頻率偿警,即一段時(shí)間內(nèi)方法被調(diào)用的次數(shù)。當(dāng)超過(guò)一定的時(shí)間限度唯笙,如果方法的調(diào)用次數(shù)仍然不足以讓它提交給即時(shí)編譯器編譯螟蒸,那這個(gè)方法的調(diào)用計(jì)數(shù)器就會(huì)被減少一半,這個(gè)過(guò)程稱(chēng)為方法調(diào)用計(jì)數(shù)器的熱度衰減崩掘,這個(gè)時(shí)間就被稱(chēng)為半衰周期七嫌。
有哪些經(jīng)典的優(yōu)化技術(shù)(即時(shí)編譯器)?
語(yǔ)言無(wú)關(guān)的經(jīng)典優(yōu)化技術(shù)之一:公共子表達(dá)式消除
語(yǔ)言相關(guān)的經(jīng)典優(yōu)化技術(shù)之一:數(shù)組范圍檢查消除
最重要的優(yōu)化技術(shù)之一:方法內(nèi)聯(lián)
最前沿的優(yōu)化技術(shù)之一:逃逸分析
公共子表達(dá)式消除
普遍應(yīng)用于各種編譯器的經(jīng)典優(yōu)化技術(shù)苞慢,它的含義是:
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" cid="n442" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;" lang="">如果一個(gè)表達(dá)式E已經(jīng)被計(jì)算過(guò)了诵原,并且從先前的計(jì)算到現(xiàn)在E中所有變量的值都沒(méi)有發(fā)生變化,那么E的這次出現(xiàn)就成了公共子表達(dá)式。沒(méi)有必要重新計(jì)算绍赛,直接用結(jié)果代替E就可以了鞋拟。</pre>
數(shù)組邊界檢查消除
因?yàn)镴ava會(huì)自動(dòng)檢查數(shù)組越界,每次數(shù)組元素的讀寫(xiě)都帶有一次隱含的條件判定操作惹资,對(duì)于擁有大量數(shù)組訪問(wèn)的程序代碼贺纲,這無(wú)疑是一種性能負(fù)擔(dān)。
如果數(shù)組訪問(wèn)發(fā)生在循環(huán)之中褪测,并且使用循環(huán)變量來(lái)進(jìn)行數(shù)組訪問(wèn)猴誊,如果編譯器只要通過(guò)數(shù)據(jù)流分析就可以判定循環(huán)變量的取值范圍永遠(yuǎn)在數(shù)組區(qū)間內(nèi),那么整個(gè)循環(huán)中就可以把數(shù)組的上下界檢查消除掉侮措,可以節(jié)省很多次的條件判斷操作懈叹。
方法內(nèi)聯(lián)
內(nèi)聯(lián)消除了方法調(diào)用的成本,還為其他優(yōu)化手段建立良好的基礎(chǔ)分扎。
編譯器在進(jìn)行內(nèi)聯(lián)時(shí)澄成,如果是非虛方法,那么直接內(nèi)聯(lián)畏吓。如果遇到虛方法墨状,則會(huì)查詢(xún)當(dāng)前程序下是否有多個(gè)目標(biāo)版本可供選擇,如果查詢(xún)結(jié)果只有一個(gè)版本菲饼,那么也可以?xún)?nèi)聯(lián)肾砂,不過(guò)這種內(nèi)聯(lián)屬于激進(jìn)優(yōu)化,需要預(yù)留一個(gè)逃生門(mén)(Guard條件不成立時(shí)的Slow Path)宏悦,稱(chēng)為守護(hù)內(nèi)聯(lián)镐确。
如果程序的后續(xù)執(zhí)行過(guò)程中,虛擬機(jī)一直沒(méi)有加載到會(huì)令這個(gè)方法的接受者的繼承關(guān)系發(fā)現(xiàn)變化的類(lèi)饼煞,那么內(nèi)聯(lián)優(yōu)化的代碼可以一直使用源葫。否則需要拋棄掉已經(jīng)編譯的代碼,退回到解釋狀態(tài)執(zhí)行砖瞧,或者重新進(jìn)行編譯息堂。
逃逸分析
逃逸分析的基本行為就是分析對(duì)象動(dòng)態(tài)作用域:當(dāng)一個(gè)對(duì)象在方法里面被定義后,它可能被外部方法所引用芭届,這種行為被稱(chēng)為方法逃逸储矩。被外部線程訪問(wèn)到箱锐,被稱(chēng)為線程逃逸拓巧。
如果對(duì)象不會(huì)逃逸到方法或線程外,可以做什么優(yōu)化彬向?
棧上分配:一般對(duì)象都是分配在Java堆中的逃片,對(duì)于各個(gè)線程都是共享和可見(jiàn)的屡拨,只要持有這個(gè)對(duì)象的引用只酥,就可以訪問(wèn)堆中存儲(chǔ)的對(duì)象數(shù)據(jù)。但是垃圾回收和整理都會(huì)耗時(shí)呀狼,如果一個(gè)對(duì)象不會(huì)逃逸出方法裂允,可以讓這個(gè)對(duì)象在棧上分配內(nèi)存,對(duì)象所占用的內(nèi)存空間就可以隨著棧幀出棧而銷(xiāo)毀哥艇。如果能使用棧上分配绝编,那大量的對(duì)象會(huì)隨著方法的結(jié)束而自動(dòng)銷(xiāo)毀,垃圾回收的壓力會(huì)小很多貌踏。
同步消除:線程同步本身就是很耗時(shí)的過(guò)程十饥。如果逃逸分析能確定一個(gè)變量不會(huì)逃逸出線程,那這個(gè)變量的讀寫(xiě)肯定就不會(huì)有競(jìng)爭(zhēng)祖乳,同步措施就可以消除掉逗堵。
標(biāo)量替換:不創(chuàng)建這個(gè)對(duì)象,直接創(chuàng)建它的若干個(gè)被這個(gè)方法使用到的成員變量來(lái)替換眷昆。
Java與C/C++的編譯器對(duì)比
即時(shí)編譯器運(yùn)行占用的是用戶(hù)程序的運(yùn)行時(shí)間蜒秤,具有很大的時(shí)間壓力。
Java語(yǔ)言雖然沒(méi)有virtual關(guān)鍵字亚斋,但是使用虛方法的頻率遠(yuǎn)大于C++作媚,所以即時(shí)編譯器進(jìn)行優(yōu)化時(shí)難度要遠(yuǎn)遠(yuǎn)大于C++的靜態(tài)優(yōu)化編譯器。
Java語(yǔ)言是可以動(dòng)態(tài)擴(kuò)展的語(yǔ)言伞访,運(yùn)行時(shí)加載新的類(lèi)可能改變程序類(lèi)型的繼承關(guān)系掂骏,使得全局的優(yōu)化難以進(jìn)行轰驳,因?yàn)榫幾g器無(wú)法看見(jiàn)程序的全貌厚掷,編譯器不得不時(shí)刻注意并隨著類(lèi)型的變化,而在運(yùn)行時(shí)撤銷(xiāo)或重新進(jìn)行一些優(yōu)化级解。
Java語(yǔ)言對(duì)象的內(nèi)存分配是在堆上冒黑,只有方法的局部變量才能在棧上分配。C++的對(duì)象有多種內(nèi)存分配方式勤哗。
物理機(jī)如何處理并發(fā)問(wèn)題抡爹?
運(yùn)算任務(wù),除了需要處理器計(jì)算之外芒划,還需要與內(nèi)存交互冬竟,如讀取運(yùn)算數(shù)據(jù)、存儲(chǔ)運(yùn)算結(jié)果等(不能僅靠寄存器來(lái)解決)民逼。 計(jì)算機(jī)的存儲(chǔ)設(shè)備和處理器的運(yùn)算速度差了幾個(gè)數(shù)量級(jí)泵殴,所以不得不加入一層讀寫(xiě)速度盡可能接近處理器運(yùn)算速度的高速緩存(Cache),作為內(nèi)存與處理器之間的緩沖:將運(yùn)算需要的數(shù)據(jù)復(fù)制到緩存中拼苍,讓運(yùn)算快速運(yùn)行笑诅。當(dāng)運(yùn)算結(jié)束后再?gòu)木彺嫱交貎?nèi)存,這樣處理器就無(wú)需等待緩慢的內(nèi)存讀寫(xiě)了。 基于高速緩存的存儲(chǔ)交互很好地解決了處理器與內(nèi)存的速度矛盾吆你,但是引入了一個(gè)新的問(wèn)題:緩存一致性弦叶。在多處理器系統(tǒng)中,每個(gè)處理器都有自己的高速緩存妇多,它們又共享同一主內(nèi)存伤哺。當(dāng)多個(gè)處理器的運(yùn)算任務(wù)都涉及同一塊主內(nèi)存時(shí),可能導(dǎo)致各自的緩存數(shù)據(jù)不一致者祖。 為了解決一致性的問(wèn)題默责,需要各個(gè)處理器訪問(wèn)緩存時(shí)遵循緩存一致性協(xié)議。同時(shí)為了使得處理器充分被利用咸包,處理器可能會(huì)對(duì)輸出代碼進(jìn)行亂序執(zhí)行優(yōu)化桃序。Java虛擬機(jī)的即時(shí)編譯器也有類(lèi)似的指令重排序優(yōu)化。
Java 內(nèi)存模型
什么是Java內(nèi)存模型烂瘫?
Java虛擬機(jī)的規(guī)范媒熊,用來(lái)屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,以實(shí)現(xiàn)讓Java程序在各個(gè)平臺(tái)下都能達(dá)到一致的并發(fā)效果坟比。
Java內(nèi)存模型的目標(biāo)芦鳍?
定義程序中各個(gè)變量的訪問(wèn)規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出這樣的底層細(xì)節(jié)葛账。此處的變量包括實(shí)例字段柠衅、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素,但是不包括局部變量和方法參數(shù)籍琳,因?yàn)檫@些是線程私有的菲宴,不會(huì)被共享,所以不存在競(jìng)爭(zhēng)問(wèn)題趋急。
主內(nèi)存與工作內(nèi)存
所以的變量都存儲(chǔ)在主內(nèi)存喝峦,每條線程還有自己的工作內(nèi)存,保存了被該線程使用到的變量的主內(nèi)存副本拷貝呜达。線程對(duì)變量的所有操作(讀取谣蠢、賦值)都必須在工作內(nèi)存中進(jìn)行,不能直接讀寫(xiě)主內(nèi)存的變量查近。不同的線程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存的變量眉踱,線程間變量值的傳遞需要通過(guò)主內(nèi)存。
[圖片上傳失敗...(image-2b982c-1605629836838)]
內(nèi)存間的交互操作
一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存霜威、如何從工作內(nèi)存同步回主內(nèi)存谈喳,Java內(nèi)存模型定義了8種操作:
[圖片上傳失敗...(image-b060ac-1605629836838)]
原子性、可見(jiàn)性侥祭、有序性
原子性:對(duì)基本數(shù)據(jù)類(lèi)型的訪問(wèn)和讀寫(xiě)是具備原子性的叁执。對(duì)于更大范圍的原子性保證茄厘,可以使用字節(jié)碼指令monitorenter和monitorexit來(lái)隱式使用lock和unlock操作。這兩個(gè)字節(jié)碼指令反映到Java代碼中就是同步塊——synchronized關(guān)鍵字谈宛。因此synchronized塊之間的操作也具有原子性次哈。
可見(jiàn)性:當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改吆录。Java內(nèi)存模型是通過(guò)在變量修改后將新值同步回主內(nèi)存窑滞,在變量讀取之前從主內(nèi)存刷新變量值來(lái)實(shí)現(xiàn)可見(jiàn)性的。volatile的特殊規(guī)則保證了新值能夠立即同步到主內(nèi)存恢筝,每次使用前立即從主內(nèi)存刷新哀卫。synchronized和final也能實(shí)現(xiàn)可見(jiàn)性。final修飾的字段在構(gòu)造器中一旦被初始化完成撬槽,并且構(gòu)造器沒(méi)有把this的引用傳遞出去此改,那么其他線程中就能看見(jiàn)final字段的值。
有序性:Java程序的有序性可以總結(jié)為一句話侄柔,如果在本線程內(nèi)觀察共啃,所有的操作都是有序的(線程內(nèi)表現(xiàn)為串行的語(yǔ)義);如果在一個(gè)線程中觀察另一個(gè)線程暂题,所有的操作都是無(wú)序的(指令重排序和工作內(nèi)存與主內(nèi)存同步延遲線性)移剪。
volatile
什么是volatile?
關(guān)鍵字volatile是Java虛擬機(jī)提供的最輕量級(jí)的同步機(jī)制薪者。當(dāng)一個(gè)變量被定義成volatile之后纵苛,具備兩種特性:
保證此變量對(duì)所有線程的可見(jiàn)性。當(dāng)一條線程修改了這個(gè)變量的值言津,新值對(duì)于其他線程是可以立即得知的攻人。而普通變量做不到這一點(diǎn)。
禁止指令重排序優(yōu)化纺念。普通變量?jī)H僅能保證在該方法執(zhí)行過(guò)程中贝椿,得到正確結(jié)果,但是不保證程序代碼的執(zhí)行順序陷谱。
為什么基于volatile變量的運(yùn)算在并發(fā)下不一定是安全的?
volatile變量在各個(gè)線程的工作內(nèi)存瑟蜈,不存在一致性問(wèn)題(各個(gè)線程的工作內(nèi)存中volatile變量烟逊,每次使用前都要刷新到主內(nèi)存)。但是Java里面的運(yùn)算并非原子操作铺根,導(dǎo)致volatile變量的運(yùn)算在并發(fā)下一樣是不安全的宪躯。
為什么使用volatile?
在某些情況下位迂,volatile同步機(jī)制的性能要優(yōu)于鎖(synchronized關(guān)鍵字)访雪,但是由于虛擬機(jī)對(duì)鎖實(shí)行的許多消除和優(yōu)化详瑞,所以并不是很快。
volatile變量讀操作的性能消耗與普通變量幾乎沒(méi)有差別臣缀,但是寫(xiě)操作則可能慢一些坝橡,因?yàn)樗枰诒镜卮a中插入許多內(nèi)存屏障指令來(lái)保證處理器不發(fā)生亂序執(zhí)行。
并發(fā)與線程
并發(fā)與線程的關(guān)系精置?
并發(fā)不一定要依賴(lài)多線程计寇,PHP中有多進(jìn)程并發(fā)。但是Java里面的并發(fā)是多線程的脂倦。
什么是線程番宁?
線程是比進(jìn)程更輕量級(jí)的調(diào)度執(zhí)行單位。線程可以把一個(gè)進(jìn)程的資源分配和執(zhí)行調(diào)度分開(kāi)赖阻,各個(gè)線程既可以共享進(jìn)程資源(內(nèi)存地址蝶押、文件I/O),又可以獨(dú)立調(diào)度(線程是CPU調(diào)度的最基本單位)火欧。
實(shí)現(xiàn)線程有哪些方式播聪?
使用內(nèi)核線程實(shí)現(xiàn)
使用用戶(hù)線程實(shí)現(xiàn)
使用用戶(hù)線程+輕量級(jí)進(jìn)程混合實(shí)現(xiàn)
Java線程的實(shí)現(xiàn)
操作系統(tǒng)支持怎樣的線程模型,在很大程度上就決定了Java虛擬機(jī)的線程是怎樣映射的布隔。
Java線程調(diào)度
什么是線程調(diào)度离陶?
線程調(diào)度是系統(tǒng)為線程分配處理器使用權(quán)的過(guò)程。
線程調(diào)度有哪些方法衅檀?
協(xié)同式線程調(diào)度:實(shí)現(xiàn)簡(jiǎn)單招刨,沒(méi)有線程同步的問(wèn)題。但是線程執(zhí)行時(shí)間不可控哀军,容易系統(tǒng)崩潰沉眶。
搶占式線程調(diào)度:每個(gè)線程由系統(tǒng)來(lái)分配執(zhí)行時(shí)間,不會(huì)有線程導(dǎo)致整個(gè)進(jìn)程阻塞的問(wèn)題杉适。
雖然Java線程調(diào)度是系統(tǒng)自動(dòng)完成的谎倔,但是我們可以建議系統(tǒng)給某些線程多分配點(diǎn)時(shí)間——設(shè)置線程優(yōu)先級(jí)。Java語(yǔ)言有10個(gè)級(jí)別的線程優(yōu)先級(jí)猿推,優(yōu)先級(jí)越高的線程片习,越容易被系統(tǒng)選擇執(zhí)行。
但是并不能完全依靠線程優(yōu)先級(jí)蹬叭。因?yàn)镴ava的線程是被映射到系統(tǒng)的原生線程上藕咏,所以線程調(diào)度最終還是由操作系統(tǒng)說(shuō)了算。如Windows中只有7種優(yōu)先級(jí)秽五,所以Java不得不出現(xiàn)幾個(gè)優(yōu)先級(jí)相同的情況孽查。同時(shí)優(yōu)先級(jí)可能會(huì)被系統(tǒng)自行改變。Windows系統(tǒng)中存在一個(gè)“優(yōu)先級(jí)推進(jìn)器”坦喘,當(dāng)系統(tǒng)發(fā)現(xiàn)一個(gè)線程執(zhí)行特別勤奮盲再,可能會(huì)越過(guò)線程優(yōu)先級(jí)為它分配執(zhí)行時(shí)間西设。
線程安全的定義?
當(dāng)多個(gè)線程訪問(wèn)一個(gè)對(duì)象時(shí)答朋,如果不用考慮這些線程在運(yùn)行時(shí)環(huán)境下的調(diào)度和交替執(zhí)行贷揽,也不需要進(jìn)行額外的同步,或者在調(diào)用方法進(jìn)行任何其他的協(xié)調(diào)操作绿映,調(diào)用這個(gè)對(duì)象的行為都可以獲得正確的結(jié)果擒滑,那這個(gè)對(duì)象就是線程安全的。
Java語(yǔ)言操作的共享數(shù)據(jù)叉弦,包括哪些丐一?
不可變
絕對(duì)線程安全
相對(duì)線程安全
線程兼容
線程對(duì)立
不可變
在Java語(yǔ)言里,不可變的對(duì)象一定是線程安全的淹冰,只要一個(gè)不可變的對(duì)象被正確構(gòu)建出來(lái)库车,那其外部的可見(jiàn)狀態(tài)永遠(yuǎn)也不會(huì)改變,永遠(yuǎn)也不會(huì)在多個(gè)線程中處于不一致的狀態(tài)樱拴。
如何實(shí)現(xiàn)線程安全柠衍?
虛擬機(jī)提供了同步和鎖機(jī)制。
阻塞同步(互斥同步)
非阻塞同步
阻塞同步(互斥同步)
互斥是實(shí)現(xiàn)同步的一種手段晶乔,臨界區(qū)珍坊、互斥量和信號(hào)量都是主要的互斥實(shí)現(xiàn)方式。Java中最基本的同步手段就是synchronized關(guān)鍵字正罢,其編譯后會(huì)在同步塊的前后分別形成monitorenter和monitorexit兩個(gè)字節(jié)碼指令阵漏。這兩個(gè)字節(jié)碼都需要一個(gè)Reference類(lèi)型的參數(shù)指明要鎖定和解鎖的對(duì)象。如果Java程序中的synchronized明確指定了對(duì)象參數(shù)翻具,那么這個(gè)對(duì)象就是Reference履怯;如果沒(méi)有明確指定,那就根據(jù)synchronized修飾的是實(shí)例方法還是類(lèi)方法裆泳,去獲取對(duì)應(yīng)的對(duì)象實(shí)例或Class對(duì)象作為鎖對(duì)象叹洲。 在執(zhí)行monitorenter指令時(shí),首先要嘗試獲取對(duì)象的鎖工禾。
如果這個(gè)對(duì)象沒(méi)有鎖定运提,或者當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖,把鎖的計(jì)數(shù)器+1帜篇;當(dāng)執(zhí)行monitorexit指令時(shí)將鎖計(jì)數(shù)器-1糙捺。當(dāng)計(jì)數(shù)器為0時(shí),鎖就被釋放了笙隙。
如果獲取對(duì)象失敗了,那當(dāng)前線程就要阻塞等待坎缭,知道對(duì)象鎖被另外一個(gè)線程釋放為止竟痰。
除了synchronized之外签钩,還可以使用java.util.concurrent包中的重入鎖(ReentrantLock)來(lái)實(shí)現(xiàn)同步。ReentrantLock比synchronized增加了高級(jí)功能:等待可中斷坏快、可實(shí)現(xiàn)公平鎖铅檩、鎖可以綁定多個(gè)條件。
等待可中斷:當(dāng)持有鎖的線程長(zhǎng)期不釋放鎖的時(shí)候莽鸿,正在等待的線程可以選擇放棄等待昧旨,對(duì)處理執(zhí)行時(shí)間非常長(zhǎng)的同步塊很有用。
公平鎖:多個(gè)線程在等待同一個(gè)鎖時(shí)祥得,必須按照申請(qǐng)鎖的時(shí)間順序來(lái)依次獲得鎖兔沃。synchronized中的鎖是非公平的。
非阻塞同步
互斥同步最大的問(wèn)題级及,就是進(jìn)行線程阻塞和喚醒所帶來(lái)的性能問(wèn)題乒疏,是一種悲觀的并發(fā)策略∫梗總是認(rèn)為只要不去做正確的同步措施(加鎖)怕吴,那就肯定會(huì)出問(wèn)題,無(wú)論共享數(shù)據(jù)是否真的會(huì)出現(xiàn)競(jìng)爭(zhēng)县踢,它都要進(jìn)行加鎖转绷、用戶(hù)態(tài)核心態(tài)轉(zhuǎn)換、維護(hù)鎖計(jì)數(shù)器和檢查是否有被阻塞的線程需要被喚醒等操作硼啤。
隨著硬件指令集的發(fā)展议经,我們可以使用基于沖突檢測(cè)的樂(lè)觀并發(fā)策略。先進(jìn)行操作丙曙,如果沒(méi)有其他線程征用數(shù)據(jù)爸业,那操作就成功了;如果共享數(shù)據(jù)有征用亏镰,產(chǎn)生了沖突扯旷,那就再進(jìn)行其他的補(bǔ)償措施。這種樂(lè)觀的并發(fā)策略的許多實(shí)現(xiàn)不需要線程掛起索抓,所以被稱(chēng)為非阻塞同步钧忽。
鎖優(yōu)化是在JDK的那個(gè)版本?
JDK1.6的一個(gè)重要主題逼肯,就是高效并發(fā)耸黑。HotSpot虛擬機(jī)開(kāi)發(fā)團(tuán)隊(duì)在這個(gè)版本上,實(shí)現(xiàn)了各種鎖優(yōu)化:
適應(yīng)性自旋
鎖消除
鎖粗化
輕量級(jí)鎖
偏向鎖
為什么要提出自旋鎖篮幢?
互斥同步對(duì)性能最大的影響是阻塞的實(shí)現(xiàn)大刊,掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成,這些操作給系統(tǒng)的并發(fā)性帶來(lái)很大壓力三椿。同時(shí)很多應(yīng)用共享數(shù)據(jù)的鎖定狀態(tài)缺菌,只會(huì)持續(xù)很短的一段時(shí)間葫辐,為了這段時(shí)間去掛起和恢復(fù)線程并不值得。先不掛起線程伴郁,等一會(huì)兒耿战。
自旋鎖的原理?
如果物理機(jī)器有一個(gè)以上的處理器焊傅,能讓兩個(gè)或以上的線程同時(shí)并行執(zhí)行柴我,讓后面請(qǐng)求鎖的線程稍等一會(huì)趋观,但不放棄處理器的執(zhí)行時(shí)間授帕,看看持有鎖的線程是否很快就會(huì)釋放巷查。為了讓線程等待,我們只需讓線程執(zhí)行一個(gè)忙循環(huán)(自旋)顽爹。
自旋的缺點(diǎn)纤泵?
自旋等待本身雖然避免了線程切換的開(kāi)銷(xiāo),但它要占用處理器時(shí)間镜粤。所以如果鎖被占用的時(shí)間很短捏题,自旋等待的效果就非常好;如果時(shí)間很長(zhǎng)肉渴,那么自旋的線程只會(huì)白白消耗處理器的資源公荧。所以自旋等待的時(shí)間要有一定的限度,如果自旋超過(guò)了限定的次數(shù)仍然沒(méi)有成功獲得鎖同规,那就應(yīng)該使用傳統(tǒng)的方式掛起線程了循狰。
什么是自適應(yīng)自旋?
自旋的時(shí)間不固定了券勺,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定绪钥。
如果一個(gè)鎖對(duì)象,自旋等待剛剛成功獲得鎖关炼,并且持有鎖的線程正在運(yùn)行程腹,那么虛擬機(jī)認(rèn)為這次自旋仍然可能成功,進(jìn)而運(yùn)行自旋等待更長(zhǎng)的時(shí)間儒拂。
如果對(duì)于某個(gè)鎖寸潦,自旋很少成功,那在以后要獲取這個(gè)鎖社痛,可能省略掉自旋過(guò)程见转,以免浪費(fèi)處理器資源。
有了自適應(yīng)自旋蒜哀,隨著程序運(yùn)行和性能監(jiān)控信息的不斷完善斩箫,虛擬機(jī)對(duì)程序鎖的狀況預(yù)測(cè)就會(huì)越來(lái)越準(zhǔn)確,虛擬機(jī)也會(huì)越來(lái)越聰明。
鎖消除
鎖消除是指虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí)校焦,對(duì)一些代碼上要求同步赊抖,但被檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行消除统倒。主要根據(jù)逃逸分析寨典。
程序員怎么會(huì)在明知道不存在數(shù)據(jù)競(jìng)爭(zhēng)的情況下使用同步呢?很多不是程序員自己加入的房匆。
鎖粗化
原則上耸成,同步塊的作用范圍要盡量小。但是如果一系列的連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖浴鸿,甚至加鎖操作在循環(huán)體內(nèi)井氢,頻繁地進(jìn)行互斥同步操作也會(huì)導(dǎo)致不必要的性能損耗。
鎖粗化就是增大鎖的作用域岳链。
輕量級(jí)鎖
在沒(méi)有多線程競(jìng)爭(zhēng)的前提下花竞,減少傳統(tǒng)的重量級(jí)鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗。
偏向鎖
消除數(shù)據(jù)在無(wú)競(jìng)爭(zhēng)情況下的同步原語(yǔ)掸哑,進(jìn)一步提高程序的運(yùn)行性能约急。即在無(wú)競(jìng)爭(zhēng)的情況下,把整個(gè)同步都消除掉苗分。這個(gè)鎖會(huì)偏向于第一個(gè)獲得它的線程厌蔽,如果在接下來(lái)的執(zhí)行過(guò)程中,該鎖沒(méi)有被其他的線程獲取摔癣,則持有偏向鎖的線程將永遠(yuǎn)不需要同步奴饮。
參考:《深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐(第2版)》
冰河原創(chuàng)PDF
關(guān)注 冰河技術(shù) 微信公眾號(hào)。
回復(fù) “并發(fā)編程” 領(lǐng)取《深入理解高并發(fā)編程(第1版)》PDF文檔择浊。
回復(fù) “并發(fā)源碼” 領(lǐng)取《并發(fā)編程核心知識(shí)(源碼分析篇 第1版)》PDF文檔戴卜。
回復(fù) ”限流“ 領(lǐng)取《億級(jí)流量下的分布式解決方案》PDF文檔。
回復(fù) “設(shè)計(jì)模式” 領(lǐng)取《深入淺出Java23種設(shè)計(jì)模式》PDF文檔琢岩。
回復(fù) “Java8新特性” 領(lǐng)取 《Java8新特性教程》PDF文檔投剥。
回復(fù) “分布式存儲(chǔ)” 領(lǐng)取《跟冰河學(xué)習(xí)分布式存儲(chǔ)技術(shù)》 PDF文檔。
回復(fù) “Nginx” 領(lǐng)取《跟冰河學(xué)習(xí)Nginx技術(shù)》PDF文檔粘捎。
回復(fù) “互聯(lián)網(wǎng)工程” 領(lǐng)取《跟冰河學(xué)習(xí)互聯(lián)網(wǎng)工程技術(shù)》PDF文檔薇缅。
重磅福利
微信搜一搜【冰河技術(shù)】微信公眾號(hào),關(guān)注這個(gè)有深度的程序員攒磨,每天閱讀超硬核技術(shù)干貨泳桦,公眾號(hào)內(nèi)回復(fù)【PDF】有我準(zhǔn)備的一線大廠面試資料和我原創(chuàng)的超硬核PDF技術(shù)文檔,以及我為大家精心準(zhǔn)備的多套簡(jiǎn)歷模板(不斷更新中)娩缰,希望大家都能找到心儀的工作灸撰,學(xué)習(xí)是一條時(shí)而郁郁寡歡,時(shí)而開(kāi)懷大笑的路,加油浮毯。如果你通過(guò)努力成功進(jìn)入到了心儀的公司完疫,一定不要懈怠放松,職場(chǎng)成長(zhǎng)和新技術(shù)學(xué)習(xí)一樣债蓝,不進(jìn)則退壳鹤。如果有幸我們江湖再見(jiàn)!
另外饰迹,我開(kāi)源的各個(gè)PDF芳誓,后續(xù)我都會(huì)持續(xù)更新和維護(hù),感謝大家長(zhǎng)期以來(lái)對(duì)冰河的支持0⊙肌锹淌!
寫(xiě)在最后
如果你覺(jué)得冰河寫(xiě)的還不錯(cuò),請(qǐng)微信搜索并關(guān)注「 冰河技術(shù) 」微信公眾號(hào)赠制,跟冰河學(xué)習(xí)高并發(fā)赂摆、分布式、微服務(wù)钟些、大數(shù)據(jù)烟号、互聯(lián)網(wǎng)和云原生技術(shù),「 冰河技術(shù) 」微信公眾號(hào)更新了大量技術(shù)專(zhuān)題厘唾,每一篇技術(shù)文章干貨滿(mǎn)滿(mǎn)褥符!不少讀者已經(jīng)通過(guò)閱讀「 冰河技術(shù) 」微信公眾號(hào)文章,吊打面試官抚垃,成功跳槽到大廠喷楣;也有不少讀者實(shí)現(xiàn)了技術(shù)上的飛躍,成為公司的技術(shù)骨干鹤树!如果你也想像他們一樣提升自己的能力铣焊,實(shí)現(xiàn)技術(shù)能力的飛躍,進(jìn)大廠罕伯,升職加薪曲伊,那就關(guān)注「 冰河技術(shù) 」微信公眾號(hào)吧,每天更新超硬核技術(shù)干貨追他,讓你對(duì)如何提升技術(shù)能力不再迷茫坟募!