柚子快報(bào)邀請碼778899分享:JVM問答
零、JVM概述
1. 如何理解Java語言的跨平臺性
Java語言跨平臺是因?yàn)镴VM是跨平臺的,.java文件被編譯成.class字節(jié)碼文件后,可以在各個(gè)平臺的JVM上運(yùn)行,將其編譯成對應(yīng)平臺的機(jī)器碼。JVM從軟件層面屏蔽不同操作系統(tǒng)上再底層硬件與指令的區(qū)別,實(shí)現(xiàn)“一次編譯,到處運(yùn)行”。
2. 如何理解JVM的語言無關(guān)性
各語言文件被各自的編譯器編譯成符合字節(jié)碼規(guī)范的.class文件,而JVM只關(guān)心自己接收的這個(gè)字節(jié)碼文件。所以各語言只要能編譯成符合字節(jié)碼規(guī)范的.class文件,就能在JVM上運(yùn)行,使用JVM的許多功能。
3. 什么是JVM的解釋執(zhí)行
首先字節(jié)碼文件被加載到JVM中,在執(zhí)行引擎部分將字節(jié)碼指令逐行解釋執(zhí)行為本地機(jī)器碼指令。這個(gè)過程會用到程序計(jì)數(shù)器,記錄下一條應(yīng)該執(zhí)行哪一條字節(jié)碼指令。
4. 什么是JIT
解釋執(zhí)行的速度較慢,在JVM中存在一種情況,如果某一條字節(jié)碼指令是熱點(diǎn)代碼(調(diào)用次數(shù)多,JVM會判定其為熱點(diǎn)代碼),JIT即時(shí)解釋器會將其直接編譯為本地機(jī)器指令,不需要再逐行執(zhí)行,提高執(zhí)行效率。
一、類加載器
1. 類的加載過程是怎樣的?介紹一下類加載機(jī)制?
首先,類的加載機(jī)制就是將class文件加載到JVM內(nèi)存中,經(jīng)過一系列操作形成可以在JVM中可以使用的Java類型。
類的加載過程可以大致分為三個(gè)階段:加載、鏈接、初始化。
(1). 在加載階段,通過類的全限定名,獲取類的二進(jìn)制字節(jié)流,將其轉(zhuǎn)化為某種靜態(tài)數(shù)據(jù)結(jié)構(gòu),存到運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)中,轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),并在堆中創(chuàng)建一個(gè)代表這個(gè)類的class對象,用來封裝類在方法區(qū)的數(shù)據(jù)結(jié)構(gòu),作為訪問方法區(qū)中這些數(shù)據(jù)的入口。(先找——>存進(jìn)來——>創(chuàng)建訪問入口)
(2). 在鏈接階段,又可以分為三個(gè)部分:驗(yàn)證、準(zhǔn)備、解析
通過文件格式、元數(shù)據(jù)、字節(jié)碼、符號音樂驗(yàn)證,確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)要求,保證被加載類的正確性,不會危害虛擬機(jī)自身安全。 在準(zhǔn)備階段為靜態(tài)變量分配內(nèi)存(方法區(qū))并設(shè)置默認(rèn)的初始值(零值)。 解析階段會把類中的符號引用替換為直接引用,比如A類中引用了B,class文件包含了B的符號引用,用一個(gè)字符串代表B的地址。在運(yùn)行階段,A進(jìn)行了類加載,在解析階段發(fā)現(xiàn)B還未加載,會觸發(fā)B的類加載,將B加載到JVM中,此時(shí)A中B的符號引用會被替換成B的實(shí)際地址,即為直接引用,這樣A就能真正的調(diào)用B。但解析階段有時(shí)會發(fā)生在初始化之后,即動(dòng)態(tài)解析。在Java中通過后期綁定的方式實(shí)現(xiàn)多態(tài)。如果A調(diào)用的B是一個(gè)具體的實(shí)現(xiàn)類,就稱為靜態(tài)解析,因?yàn)榻馕龅哪繕?biāo)類型很明確。而如果使用了多態(tài),這里的B可能是一個(gè)抽象類或接口,B可能有多個(gè)具體的實(shí)現(xiàn)類,此時(shí)B的具體實(shí)現(xiàn)并不明確,也就不知道使用哪個(gè)具體類的直接引用來進(jìn)行替換。所以會等到運(yùn)行的時(shí)候發(fā)生了調(diào)用,JVM棧中會得到具體的類型信息,這時(shí)候再進(jìn)行解析,就能用明確的直接引用替換符號引用。
(3). 在初始化階段,對類中的靜態(tài)變量和成員變量初始化為指定的值,執(zhí)行靜態(tài)代碼塊。如果有直接的父類,會先初始化父類。
2. 說明幾種類加載器
(1). 啟動(dòng)類加載器Bootstrap ClassLoader:加載JAVA_HOME中 jre/lib/rt.jar 里所有的class或者Xbootclasspath選項(xiàng)指定的jar包
(2). 擴(kuò)展類加載器ExtClassLoader:加載擴(kuò)展功能的一些jar包,包括JAVA HOME中jre/lib/*.jar 或-Djava.ext.dirs指定目錄下的jar包
(3).?系統(tǒng)/應(yīng)用程序類加載器AppClassLoader:加載classpath中指定的jar包及Djava.class.path所指定目錄下的類和jar包
(4).?自定義類加載器Custom ClassLoader:通過繼承java.lang.ClassLoader,自定義類加載器,把自定義的類加載邏輯寫在findClass()方法中
3. 介紹一下雙親委派機(jī)制
雙親委派機(jī)制可以概括為向上檢查,向下委派。
如果一個(gè)類加載器收到了類加載請求,它并不會自己先去加載。而是把這個(gè)請求委托給父類的加載器去執(zhí)行。
如果父類加載器還存在其父類加載器,則進(jìn)一步向上委托,依次遞歸,請求最終將達(dá)到頂層的啟動(dòng)類加載器。
如果父類的加載器可以完成類加載任務(wù),就成功返回,倘若父類加載器無法完成此加載任務(wù),子加載器才會嘗試自己去加載,逐級向下。如果都不能加載,拋出異常ClassNotFoundException。
優(yōu)點(diǎn)是可以避免類的重復(fù)加載,保護(hù)程序安全,防止核心API被篡改。
4. 介紹一下沙箱安全機(jī)制
JVM系列(四):沙箱安全機(jī)制筆記_jvm沙箱-CSDN博客
沙箱是Java安全模型的核心。
沙箱是一個(gè)限制程序運(yùn)行的環(huán)境,主要限制系統(tǒng)資源訪問。
沙箱機(jī)制就是將 Java 代碼限定在JVM特定的運(yùn)行范圍中,并且嚴(yán)格限制代碼對本地系統(tǒng)資源訪問,通過這樣的措施來保證對代碼的有效隔離,防止對本地系統(tǒng)造成破壞。比如不授信的遠(yuǎn)程代碼。
????????多線程共享方法區(qū)(堆外內(nèi)存或元空間)和堆,程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧每個(gè)線程各一份。?
二、程序計(jì)數(shù)器:?
5.?使用程序計(jì)數(shù)器存儲字節(jié)碼指令地址的作用?為什么使用程序計(jì)數(shù)器記錄當(dāng)前線程的執(zhí)行地址?
因?yàn)镃PU需要不停地切換各個(gè)線程,而切換回來以后,必須知道接著從哪開始繼續(xù)執(zhí)行。JVM的字節(jié)碼解釋器就需要通過改變程序計(jì)數(shù)器的值來明確下一條應(yīng)該執(zhí)行什么字節(jié)碼指令。
6.?程序計(jì)數(shù)器為什么設(shè)計(jì)成線程私有?
所謂的多線程并發(fā),在一個(gè)特定時(shí)間段只會執(zhí)行其中一個(gè)線程的方法(CPU時(shí)間片),CPU會不停地做任務(wù)切換,必然導(dǎo)致經(jīng)常中斷和恢復(fù)。為了能夠準(zhǔn)確地記錄各個(gè)線程下一條要執(zhí)行的字節(jié)碼指令的地址,最好的的辦法就是為每個(gè)線程都分配一個(gè)獨(dú)立的程序計(jì)數(shù)器,各個(gè)線程進(jìn)行獨(dú)立計(jì)算,不會互相干擾。
三、虛擬機(jī)棧?
7. 舉例棧溢出StackOverFlowError的情況
出現(xiàn)棧溢出是因?yàn)樘摂M機(jī)棧的大小固定,使用-Xss 設(shè)置線程的最大??臻g,當(dāng)線程請求的棧深度大于虛擬機(jī)所允許的深度,將會拋出StackOverFlow異常,比如遞歸,棧幀太多導(dǎo)致棧溢出。
虛擬機(jī)棧的大小還可以動(dòng)態(tài)擴(kuò)展,但當(dāng)線程太多,虛擬機(jī)棧也很多,導(dǎo)致擴(kuò)展時(shí)無法申請到足夠的內(nèi)存時(shí)會拋出OutOfMemoryError異常。
8. 調(diào)整棧的大小,就能保證不出現(xiàn)溢出嗎?
不能。理論上只是讓棧溢出發(fā)生的晚一點(diǎn)。
9. 分配的棧內(nèi)存越大越好嗎?
不是。
會讓棧溢出發(fā)生的晚一點(diǎn),但內(nèi)存空間是有限的,虛擬機(jī)棧擠占空間,線程數(shù)變少。
10. 垃圾回收是否涉及虛擬機(jī)棧?通過垃圾回收避免棧溢出可行嗎?
不涉及,不可行。
虛擬機(jī)棧通過出棧的方式,可以理解為是虛擬機(jī)棧的“垃圾回收”,不必去顯式的垃圾回收。
11.方法中定義的局部變量是否線程安全??
具體問題具體分析。看生命周期,有沒有和外部有交集。
如果這個(gè)變量的作用域只在方法內(nèi),就是安全的。作用域不止在方法內(nèi)部,不是內(nèi)部產(chǎn)生的,傳參進(jìn)來的或返回給上層調(diào)用的,生命周期沒有結(jié)束,就不是線程安全的。
逃逸分析
四、堆
1.? 對象一定在堆中創(chuàng)建嗎?
幾乎所有對象都在堆中創(chuàng)建,但不絕對。
在new對象時(shí),首先會判斷這段代碼是否是熱點(diǎn)代碼。如果不是熱點(diǎn)代碼,該對象在堆中創(chuàng)建。如果是熱點(diǎn)代碼,則會觸發(fā)JIT即時(shí)編譯,在即時(shí)編譯器內(nèi)部有一些優(yōu)化技術(shù),比如可以判斷new的這個(gè)對象是否會逃逸(逃逸出這個(gè)方法或者這個(gè)線程,被外部的方法或線程訪問)。如果會發(fā)生逃逸,則該對象在堆中創(chuàng)建。如果不會發(fā)生逃逸,再判斷能不能開啟標(biāo)量替換(堆中對象的成員變量能不能在棧中進(jìn)行替換),如果能替換,對象會在棧上分配,如果不能替換,則該對象在堆中分配。
2. 什么是堆內(nèi)存?堆內(nèi)存包含哪些部分?
3. 什么是內(nèi)存溢出?
4. 什么是內(nèi)存泄漏?與內(nèi)存溢出的區(qū)別??
垃圾回收
1. 十種垃圾回收器
1. Serial和Serial Old是JVM最初的垃圾回收器,分別處理堆中的新生代和老年代區(qū)域,是單線程獨(dú)占式的垃圾回收器,垃圾回收的速度也相對較慢,已經(jīng)不能滿足現(xiàn)在的需求。
JVM又誕生了多線程垃圾回收器。
2. jdk 1.8 版本默認(rèn)是Parallel Scavenge和Parallel Old這對組合分別處理新生代和老年代,是Java8吞吐量最高的。
3. 如果想要減少垃圾回收的停頓時(shí)間,可以使用CMS垃圾回收器處理老年代,對應(yīng)處理新生代的組合是ParNew。之前的垃圾回收都是通過暫停線程來進(jìn)行的,而CMS進(jìn)來減少業(yè)務(wù)線程的暫停,從而達(dá)到最小的停頓時(shí)間。但CMS也有一些問題,比如浮動(dòng)垃圾、內(nèi)存碎片。所以JVM又發(fā)展出了G1垃圾回收器。
4. G1垃圾回收器不再嚴(yán)格的區(qū)分新生代和老年代,而在內(nèi)部有一個(gè)分區(qū)模型(比如堆有2G的空間,分1000個(gè)大小相等相互獨(dú)立的區(qū),每個(gè)區(qū)2兆,在這個(gè)2兆的區(qū)空間進(jìn)行垃圾回收),從Java9開始,G1成為JVM默認(rèn)的垃圾回收器。但G1的問題是無法應(yīng)對堆空間特別大的情況,此時(shí)應(yīng)該使用ZGC垃圾回收器。
5. ZGC是一種針對大空間的垃圾回收器,同時(shí)它的停頓時(shí)間非常短,可以在1ms以內(nèi),幾乎感受不到,可以很廣泛的滿足業(yè)務(wù)需求,尤其是對延遲要求高的系統(tǒng)。
6. Shenandoah垃圾回收器與ZGC相似,區(qū)別在于它是外部引入的,加入到JVM中的。
7. Eplison一般不用于垃圾回收,主要用于調(diào)試、測試JVM功能是否正常。
2. 介紹一下CMS垃圾回收器
3. 介紹一下G1垃圾回收器
4. 介紹一下JVM中的垃圾回收算法
JVM從誕生到現(xiàn)在共有三種垃圾回收算法:復(fù)制算法、標(biāo)記清除算法、標(biāo)記整理算法。
復(fù)制算法:將可用的內(nèi)存一分為二,然后先只使用其中一塊,每次在垃圾回收時(shí),只需清理這一部分內(nèi)存,再將存活的對象復(fù)制到另一塊空的內(nèi)存中。優(yōu)點(diǎn)是效率較高,適用于新生代;缺點(diǎn)是空間利用率只有50%。 標(biāo)記清除算法:根據(jù)可達(dá)性分析進(jìn)行標(biāo)記:可回收、不可回收、未分配三種狀態(tài)。每次在垃圾回收時(shí),清理被標(biāo)記為可回收的垃圾。優(yōu)點(diǎn)是空間利用率比復(fù)制算法高;缺點(diǎn)是垃圾回收后,內(nèi)存區(qū)域不連續(xù),存在內(nèi)存碎片的問題,可能會導(dǎo)致命名內(nèi)存空間足夠,但無法分配給大對象。 標(biāo)記整理算法:在標(biāo)記清除算法的基礎(chǔ)上,在垃圾回收后,將存活對象移動(dòng)到堆的一端,完成內(nèi)存的整理。優(yōu)點(diǎn)是空間利用率較好,沒有內(nèi)存碎片;缺點(diǎn)是效率低。
5. 什么是可達(dá)性分析算法?
進(jìn)行垃圾回收之前,首先要判斷這個(gè)對象是否存活、是不是垃圾。JVM默認(rèn)使用可達(dá)性分析算法來進(jìn)行判斷。
可達(dá)性分析算法會創(chuàng)建一些根,根的集合稱為GC Roots,主要包括局部變量、靜態(tài)變量、常量、JNI指針。在進(jìn)行垃圾回收時(shí),JVM會先找到這四種類型的對象,進(jìn)行可達(dá)性的鏈路分析。如果堆中的對象存在和這四種對象的鏈路,則判斷為可達(dá),不是垃圾,不進(jìn)行回收;不可達(dá)的將視為垃圾被回收。
可達(dá)性分析算法顯著優(yōu)于引用計(jì)數(shù)算法的地方在于:可以解決對象間循環(huán)引用(成環(huán))的問題。
6. 什么是垃圾回收中的STW?
STW全稱為Stop The Word,指的是垃圾回收過程中的暫停。
在業(yè)務(wù)線程運(yùn)行時(shí),觸發(fā)了垃圾回收,此時(shí)需要暫停業(yè)務(wù)線程,啟動(dòng)垃圾回收相關(guān)的線程,完成垃圾回收、空間清理操作。當(dāng)垃圾回收執(zhí)行完畢,重新開始業(yè)務(wù)線程。業(yè)務(wù)線程暫停的這段時(shí)間就是STW,會造成業(yè)務(wù)的卡頓,需盡量減少這段停頓時(shí)間。(ZGC垃圾回收器的主要目的就是減少STW)
7. 介紹一下JVM的分代模型?
JVM有三種垃圾回收算法,各有所長各有所短。JVM為了保證垃圾回收的效率,不會單一使用某一個(gè)垃圾回收算法,而是使用分代模型,將堆空間分為新生代(Eden、From、To)和老年代。
絕大部分對象是“朝生夕死”,復(fù)制算法的效率最高,所以在新生代使用復(fù)制算法。如果某個(gè)對象,在經(jīng)歷了15次垃圾回收仍然存活,說明其下一次被回收的幾率很小,不適合再使用復(fù)制算法來回復(fù)制了,需要再劃分出一塊區(qū)域稱為老年代,用來存放多次垃圾回收仍然沒有被回收的對象,使用標(biāo)記清除算法或標(biāo)記整理算法。
分代模型可以在不同的代里面采樣不同的垃圾回收算法,確保整體垃圾回收的高效。
柚子快報(bào)邀請碼778899分享:JVM問答
相關(guān)鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。