1.JVM為什么可以跨平臺(tái)
JVM能跨計(jì)算機(jī)體系結(jié)構(gòu)(操作系統(tǒng))來執(zhí)行Java字節(jié)碼(JVM字節(jié)碼指令集),屏蔽可與各個(gè)計(jì)算機(jī)平臺(tái)相關(guān)的軟件或者硬件之間的差異,使得與平臺(tái)相關(guān)的耦合統(tǒng)一由JVM提供者來實(shí)現(xiàn)。
指令集:計(jì)算機(jī)所能識(shí)別的機(jī)器語言的命令集合。
每個(gè)運(yùn)行中的Java程序都是一個(gè)JVM實(shí)例岭参。
2.描述JVM體系結(jié)構(gòu)
(1)類加載器:JVM啟動(dòng)時(shí)或者類運(yùn)行時(shí)將需要的class加載到JVM中。每個(gè)被裝載的類的類型對(duì)應(yīng)一個(gè)Class實(shí)例尝艘,唯一表示該類演侯,存于堆中。
(2)執(zhí)行引擎:負(fù)責(zé)執(zhí)行JVM的字節(jié)碼指令(CPU)背亥。執(zhí)行引擎是JVM的核心部分蚌本,作用是解析字節(jié)碼指令,得到執(zhí)行結(jié)果(實(shí)現(xiàn)方式:直接執(zhí)行隘梨,JIT(just in time)即時(shí)編譯轉(zhuǎn)成本地代碼執(zhí)行,寄存器芯片模式執(zhí)行舷嗡,基于棧執(zhí)行)轴猎。本質(zhì)上就是一個(gè)個(gè)方法串起來的流程。每個(gè)Java線程就是一個(gè)執(zhí)行引擎的實(shí)例进萄,一個(gè)JVM實(shí)例中會(huì)有多個(gè)執(zhí)行引擎在工作捻脖,有的執(zhí)行用戶程序,有的執(zhí)行JVM內(nèi)部程序(GC).
(3)內(nèi)存區(qū):模擬物理機(jī)的存儲(chǔ)中鼠、記錄和調(diào)度等功能模塊可婶,如寄存器或者PC指針記錄器。存儲(chǔ)執(zhí)行引擎執(zhí)行時(shí)所需要存儲(chǔ)的數(shù)據(jù)援雇。
(4)本地方法接口:調(diào)用操作系統(tǒng)本地方法返回結(jié)果矛渴。
3.描述JVM工作機(jī)制
機(jī)器如何執(zhí)行代碼:源代碼-預(yù)處理器-編譯器-匯編程序-目標(biāo)代碼-鏈接器-可執(zhí)行程序。
Java編譯器將高級(jí)語言編譯成虛擬機(jī)目標(biāo)語言。
JVM執(zhí)行字節(jié)碼指令是基于棧的架構(gòu)具温,所有的操作數(shù)必須先入棧蚕涤,然后根據(jù)操作碼選擇從棧頂彈出若干元素進(jìn)行計(jì)算后將結(jié)果壓入棧中。
通過Java編譯器將源代碼編譯成虛擬機(jī)目標(biāo)語言铣猩,然后通過JVM執(zhí)行引擎執(zhí)行揖铜。
4.為何JVM字節(jié)碼指令選擇基于棧的結(jié)構(gòu)
JVM要設(shè)計(jì)成平臺(tái)無關(guān)性,很難設(shè)計(jì)統(tǒng)一的基于寄存器的指令达皿。
為了指令的緊湊性天吓,讓編譯后的class文件更加緊湊,提高字節(jié)碼在網(wǎng)絡(luò)上的傳輸效率峦椰。
5.描述執(zhí)行引擎的架構(gòu)設(shè)計(jì)
創(chuàng)建新線程時(shí)龄寞,JVM會(huì)為這個(gè)線程創(chuàng)建一個(gè)棧,同時(shí)分配一個(gè)PC寄存器(指向第一行可執(zhí)行的代碼)们何。調(diào)用新方法時(shí)會(huì)在這個(gè)棧上創(chuàng)建新的棧幀數(shù)據(jù)結(jié)構(gòu)萄焦。執(zhí)行完成后方法對(duì)應(yīng)的棧幀將消失,PC寄存器被銷毀冤竹,局部變量區(qū)所有值被釋放拂封,被JVM回收。
6. 描述javac編譯器的基本結(jié)構(gòu)
Javac編譯器的作用是將符合Java語言規(guī)范的的源代碼轉(zhuǎn)換成JVM規(guī)范的Java字節(jié)碼鹦蠕。
(1)詞法分析器組件:找出規(guī)范化的Token流
(2)語法分析器組件:生成符合Java語言規(guī)范的抽象語法樹
(3)語義分析器組件:將復(fù)雜的語法轉(zhuǎn)化成最簡單的語法冒签,注解語法樹
(4)代碼生成器組件:將語法樹數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化成字節(jié)碼數(shù)據(jù)結(jié)構(gòu)
7.描述JVM編譯優(yōu)化
早期(編譯器):
很少;編譯時(shí)钟病,為節(jié)省常量池空間萧恕,能確定的相同常量只用一個(gè)引用地址。
晚期(運(yùn)行期):
方法內(nèi)聯(lián):去除方法調(diào)用的成本肠阱;為其他優(yōu)化建立良好基礎(chǔ)票唆,便于在更大范圍采取連續(xù)優(yōu)化的手段。
冗余訪問消除:公共子表達(dá)式消除
復(fù)寫傳播:完全相等的變量可替代
無用代碼消除:清除永遠(yuǎn)不會(huì)執(zhí)行的代碼
? ? ? (1)公共子表達(dá)式消除(語言無關(guān)):如果公共子表達(dá)式已經(jīng)計(jì)算過了屹徘,并且沒有變化走趋,那就沒有必要再次計(jì)算,可用結(jié)果替換噪伊。
? ? ? (2)數(shù)組邊界檢查消除(語言相關(guān)):限定循環(huán)變量在取值范圍之間簿煌,可節(jié)省多次條件判斷。
? ? ? (3)方法內(nèi)聯(lián)(最重要):去除方法調(diào)用的成本鉴吹;為其他優(yōu)化建立良好基礎(chǔ)姨伟,便于在更大范圍采取連續(xù)優(yōu)化的手段。
? ? ? (4)逃逸分析(最前沿):分析對(duì)象的動(dòng)態(tài)作用域豆励;變量作為調(diào)用參數(shù)傳遞到其他方法中-方法逃逸夺荒;被外部線程訪問-線程逃逸。
棧上分配-減少垃圾系統(tǒng)收集壓力
同步消除-如果無法逃逸出線程,則可以消除同步
標(biāo)量替換-將變量恢復(fù)原始類型來訪問
小抄:final修飾的局部變量和參數(shù)般堆,在常量池中沒有符號(hào)引用在孝,沒有訪問標(biāo)識(shí),對(duì)運(yùn)行期是沒有任何影響的淮摔,僅僅保證其編譯期間的不變性私沮。
8.ClassLoader(類加載器)有哪些
(1)Bootstrap ClassLoader(啟動(dòng)類加載器):完全由JVM控制,加載JVM自身工作需要的類(JAVA_HOME/lib)
(2)Extension ClassLoader(擴(kuò)展類加載器):屬于JVM自身一部分和橙,不是JVM自身實(shí)現(xiàn)的(JAVA_HOME/lib/ext)
(3)Appclication ClassLoader(應(yīng)用程序類加載器):父類是Extension ClassLoader仔燕,加載Classpath(用戶類路徑)上的類庫
9.描述ClassLoader的作用(什么是類加載器)和加載過程
將Class文件加載到JVM中、審查每個(gè)類由誰加載(父優(yōu)先的等級(jí)加載機(jī)制)魔招、將Class字節(jié)碼重新解析成JVM統(tǒng)一要求的對(duì)象(Class對(duì)象)格式晰搀。
.class->findclass->Liking:Class規(guī)范驗(yàn)證、準(zhǔn)備办斑、解析->類屬性初始化賦值(static塊的執(zhí)行)->Class對(duì)象(這也就是為什么靜態(tài)塊只執(zhí)行一次)
10.描述JVM類加載機(jī)制
ClassLoader首先不會(huì)自己嘗試去加載類外恕,而是把這個(gè)請(qǐng)求委托給父類加載器完成,每一個(gè)層次都是乡翅。只有當(dāng)父加載器反饋無法完成請(qǐng)求時(shí)(在搜索范圍內(nèi)沒有找到所需的類)鳞疲,子加載器才會(huì)嘗試加載(等級(jí)加載機(jī)制、父優(yōu)先蠕蚜、雙親委派)尚洽。
好處:類隨著它的加載器一起具有一種帶有優(yōu)先級(jí)的層次關(guān)系;保證同一個(gè)類只能被一個(gè)加載器加載靶累。
11.JVM加載class文件到內(nèi)存的兩種方式
(1)隱式加載:繼承或者引用的類不在內(nèi)存中
(2)顯式加載:代碼中通過調(diào)用ClassLoader加載
12.加載類錯(cuò)誤分析及其解決
(1)ClassNotFoundException:沒有找到對(duì)應(yīng)的字節(jié)碼(.class)文件;檢查classpath下有無對(duì)應(yīng)文件
(2)NoClassDefFoundError:隱式加載時(shí)沒有找到腺毫,ClassNotFoundException引發(fā)NoClassDefFoundError;確保每個(gè)類引用的類都在classpath下
(3)UnsatisfiedLinkError:(未滿足鏈接錯(cuò)誤)刪除了JVM的某個(gè)lib文件或者解析native標(biāo)識(shí)的方法時(shí)找不到對(duì)應(yīng)的本地庫文件
(4)ClassCastException:強(qiáng)制類型轉(zhuǎn)換時(shí)出現(xiàn)這個(gè)錯(cuò)誤挣柬;容器類型最好顯示指明其所包含對(duì)象類型潮酒、先instanceof檢查是不是目標(biāo)類型,再類型轉(zhuǎn)換
(5)ExceptionInitializerError:給類的靜態(tài)屬性賦值時(shí)
13:Java應(yīng)不應(yīng)該動(dòng)態(tài)加載類(JVM能不能動(dòng)態(tài)加載類)
JVM中對(duì)象只有一份邪蛔,不能被替換急黎,對(duì)象的引用關(guān)系只有對(duì)象的創(chuàng)建者持有和使用,JVM不可干預(yù)對(duì)象的引用關(guān)系店溢,因?yàn)镴VM不知道對(duì)象是怎么被使用的,JVM不知道對(duì)象的運(yùn)行時(shí)類型委乌,只知道編譯時(shí)類型床牧。
但是可以不保存對(duì)象的狀態(tài),對(duì)象創(chuàng)建和使用后就被釋放掉遭贸,下次修改后戈咳,對(duì)象就是新的了(JSP)。
14.Java中哪些組件需要使用內(nèi)存
(1)Java堆:存儲(chǔ)Java對(duì)象
(2)線程:Java運(yùn)行程序的實(shí)體
(3)類和類加載器:存儲(chǔ)在堆中,這部分區(qū)域叫永久代(PermGen區(qū))
(4)NIO:基于通道和緩沖區(qū)來執(zhí)行I/O的新方式著蛙。
(5)JNI:本地代碼可以調(diào)用Java方法删铃,Java方法也可以調(diào)用本地代碼
15.描述JVM內(nèi)存結(jié)構(gòu)及內(nèi)存溢出。
JVM是按照運(yùn)行時(shí)數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu)來劃分內(nèi)存結(jié)構(gòu)的踏堡。
PC寄存器數(shù)據(jù):嚴(yán)格來說是一個(gè)數(shù)據(jù)結(jié)構(gòu)猎唁,保存當(dāng)前正在執(zhí)行的程序的內(nèi)存地址。為了線程切換后能恢復(fù)到正確的執(zhí)行位置顷蟆,線程私有诫隅。不會(huì)內(nèi)存溢出。
(1)Java棧:方法執(zhí)行的內(nèi)存模型帐偎,存儲(chǔ)線程執(zhí)行所需要的數(shù)據(jù)逐纬。線程私有。
--OutOfMemoryError:JVM擴(kuò)展棧時(shí)無法申請(qǐng)到足夠的空間削樊。一個(gè)不斷調(diào)用自身而不會(huì)終止的方法豁生。
--StackOverflowError:請(qǐng)求的棧深度大于JVM所允許的棧深度。創(chuàng)建足夠多的線程漫贞。
(2)堆:存儲(chǔ)對(duì)象甸箱,每一個(gè)存在堆中Java對(duì)象都是這個(gè)對(duì)象的類的副本,復(fù)制包括繼承自他父類的所有非靜態(tài)屬性绕辖。線程共享摇肌。
--OutOfMemoryError:對(duì)象數(shù)量到達(dá)堆容量限制∫羌剩可通過不斷向ArrayList中添加對(duì)象實(shí)現(xiàn)围小。
(3)方法區(qū):存儲(chǔ)類結(jié)構(gòu)信息。包括常量池(編譯期生產(chǎn)的各種字面量和符號(hào)引用)和運(yùn)行時(shí)常量池树碱。線程共享肯适。
--OutOfMemoryError:同運(yùn)行時(shí)常量池。
(4)本地方法棧:與Java棧類似成榜,為JVM運(yùn)行Native方法準(zhǔn)備的空間框舔。線程私有。(C棧)OutOfMemoryError和StackOverflowError同JVM棧赎婚。
(5)運(yùn)行時(shí)常量池:代表運(yùn)行時(shí)每個(gè)class文件中的常量表刘绣。運(yùn)行期間產(chǎn)生的新的常量放入運(yùn)行時(shí)常量池。
--OutOfMemoryError:不斷向List中添加字符串挣输,然后String.inern()纬凤,PermGen Space(運(yùn)行時(shí)常量池屬于方法區(qū))。
(6)本地直接內(nèi)存:即NIO撩嚼。
--OutOfMemoryError:通過直接向操作系統(tǒng)申請(qǐng)分配內(nèi)存停士。
16.描述JVM內(nèi)存分配策略
(1)對(duì)象優(yōu)先分配在Eden
(2)大對(duì)象直接進(jìn)入老年代
(3)長期存活的對(duì)象將進(jìn)入老年代
(4)幸存區(qū)相同年齡對(duì)象的占幸存區(qū)空間的多于其一半挖帘,將進(jìn)入老年代
(5)空間擔(dān)保分配(老年代剩余空間需多于幸存區(qū)的一半,否則要Full GC)
17.描述JVM如何檢測(cè)垃圾
通過可達(dá)性分析算法恋技,通過一些列稱為GC Roots的對(duì)象作為起始點(diǎn)拇舀,從這些起始點(diǎn)向下搜索,搜索所走過的路徑稱為引用鏈蜻底,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連(GC Roots到這個(gè)對(duì)象不可達(dá))骄崩,則證明這個(gè)對(duì)象是不可用的。
使用可達(dá)性分析算法而不是引用計(jì)數(shù)算法朱躺。因?yàn)橐糜?jì)數(shù)算法很難解決對(duì)象之間相互循環(huán)引用的問題刁赖。
18.哪些元素可作為GC Roots
(1)JVM棧(棧幀中的本地變量表)中的引用
(2)方法區(qū)中類靜態(tài)屬性引用
(3)方法區(qū)中常量引用
(4)本地方法棧中JNI(一般的Native方法)引用
19.描述分代垃圾收集算法的思路:
把對(duì)象按照壽命長短來分組,分為年輕代和老年代长搀,新創(chuàng)建的在老年代宇弛,經(jīng)歷幾次回收后仍然存活的對(duì)象進(jìn)入老年代,老年代的垃圾頻率不像年輕代那樣頻繁源请,減少每次收集都去掃描所有對(duì)象的數(shù)量枪芒,提高垃圾回收效率。
20.描述基于分代的堆結(jié)構(gòu)及其比例谁尸。
(1)年輕代(Young區(qū)-1/4):Eden+Survior(1/8舅踪,這個(gè)比例保證只有10%的空間被浪費(fèi),保證每次回收都只有不多于10%的對(duì)象存活)=From+To良蛮,存放新創(chuàng)建的對(duì)象.
(2)老年代(Old區(qū) ):存放幾次垃圾收集后存活的對(duì)象
(3)永久區(qū)(Perm區(qū)):存放類的Class對(duì)象
21.描述垃圾收集算法
(1)標(biāo)記-清除算法:首先標(biāo)記處所要回收的對(duì)象抽碌,標(biāo)記完成后統(tǒng)一清除。缺點(diǎn):標(biāo)記效率低决瞳,清除效率低货徙,回收結(jié)束后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片(沒有足夠連續(xù)空間分配內(nèi)存,提前觸發(fā)另一次垃圾回收)皮胡。適用于對(duì)象存活率高的老年代痴颊。
(2)復(fù)制算法(Survivor的from和to區(qū),from和to會(huì)互換角色):
將內(nèi)存容量劃分大小相等的兩塊屡贺,每次只使用其中一塊蠢棱。一塊用完,就將存活的對(duì)象復(fù)制到另一塊甩栈,然后把使用過的一塊一次清除泻仙。不用考慮內(nèi)存碎片,每次只要移動(dòng)頂端指針量没,按順序分配內(nèi)存即可玉转,實(shí)現(xiàn)簡單運(yùn)行高效。適用于新生代允蜈。
缺點(diǎn):內(nèi)存縮小為原來的一般冤吨,代價(jià)高。浪費(fèi)50%的空間饶套。
(3)標(biāo)記-整理算法:
標(biāo)記完成后漩蟆,將存活的對(duì)象移動(dòng)到一端,然后清除邊界以外的內(nèi)存妓蛮。適用于對(duì)象存活率高的老年代怠李。
22.描述新生代和老年代的回收策略
Eden區(qū)滿后觸發(fā)minor GC,將所有存活對(duì)象復(fù)制到一個(gè)Survivor區(qū),另一Survivor區(qū)存活的對(duì)象也復(fù)制到這個(gè)Survivor區(qū)中蛤克,始終保證有一個(gè)Survivor是空的捺癞。
Toung區(qū)Survivor滿后觸發(fā)minor GC后仍然存活的對(duì)象存到Old區(qū),如果Survivor區(qū)放不下Eden區(qū)的對(duì)象或者Survivor區(qū)對(duì)象足夠老了构挤,直接放入Old區(qū)髓介,如果Old區(qū)放不下則觸發(fā)Full GC。
Perm區(qū)滿將觸發(fā)Major GC筋现。
23.描述CMS垃圾收集器
CMS 收集器:Concurrent Mark Sweep 并發(fā)標(biāo)記-清除唐础。重視響應(yīng)速度,適用于互聯(lián)網(wǎng)和B/S系統(tǒng)的服務(wù)端上矾飞。初始標(biāo)記還是需要Stop the world 但是速度很快一膨。缺點(diǎn):CPU資源敏感,無法浮動(dòng)處理垃圾洒沦,會(huì)有大量空間碎片產(chǎn)生豹绪。
24.Java應(yīng)不應(yīng)該動(dòng)態(tài)記載類
Java的優(yōu)勢(shì)正是基于共享對(duì)象的機(jī)制,達(dá)到信息的高度共享申眼,也就是通過保存并持有對(duì)象的狀態(tài)而省去類信息的重復(fù)創(chuàng)建和回收瞒津。對(duì)象一旦被創(chuàng)建,就可以被人持有和利用豺型。動(dòng)態(tài)加載理論上可以直接替換這個(gè)對(duì)象仲智,然后更新Java棧中所有對(duì)原對(duì)象的引用關(guān)系,但是仍然不可行姻氨,因?yàn)檫@違反了JVM的設(shè)計(jì)原則钓辆,對(duì)象的引用只有對(duì)象的創(chuàng)建者持有和使用,JVM并不可以干預(yù)對(duì)象的引用關(guān)系肴焊,因?yàn)镴VM并不知道對(duì)象是怎么辦使用的前联,JVM并不知道對(duì)象的運(yùn)行時(shí)類型而只知道編譯時(shí)類型。
假如一個(gè)對(duì)象的屬性結(jié)構(gòu)被修改娶眷,但在運(yùn)行時(shí)其他對(duì)象可能仍然引用該屬性似嗤。
但是可以采取不保存對(duì)象的狀態(tài)的解決辦法,對(duì)象被創(chuàng)建后使用后就被釋放掉届宠,下次修改后烁落,對(duì)象也就是新的了乘粒。JSP和其他解釋型語言都是如此。