1 JVM內(nèi)存分哪幾個(gè)區(qū),每個(gè)區(qū)的作用是什么?
java虛擬機(jī)主要分為以下幾個(gè)區(qū):
(1)方法區(qū):
- a. JDK7中稱為永久代,JDK8及之后稱為元空間,在該區(qū)內(nèi)很少發(fā)生垃圾回收,但是并不代表不發(fā)生GC,在這里進(jìn)行的GC主要是對方法區(qū)里的常量池和對類型的卸載
- b. 方法區(qū)主要用來存儲已被虛擬機(jī)加載的類的信息、常量、靜態(tài)變量和即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
- c. 該區(qū)域是被線程共享的。
- d. 方法區(qū)里有一個(gè)運(yùn)行時(shí)常量池,用于存放靜態(tài)編譯產(chǎn)生的字面量和符號引用。該常量池具有動態(tài)性,也就是說常量并不一定是編譯時(shí)確定,運(yùn)行時(shí)生成的常量也會存在這個(gè)常量池中。
(2)虛擬機(jī)棧:
- a. 虛擬機(jī)棧也就是我們平常所稱的棧內(nèi)存,它為java方法服務(wù),每個(gè)方法在執(zhí)行的時(shí)候都會創(chuàng)建一個(gè)棧幀,用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接和方法出口等信息。
- b. 虛擬機(jī)棧是線程私有的,它的生命周期與線程相同。
- c. 局部變量表里存儲的是基本數(shù)據(jù)類型、returnAddress類型(指向一條字節(jié)碼指令的地址)和對象引用,這個(gè)對象引用有可能是指向?qū)ο笃鹗嫉刂返囊粋€(gè)指針,也有可能是代表對象的句柄或者與對象相關(guān)聯(lián)的位置。局部變量所需的內(nèi)存空間在編譯器間確定
- d. 操作數(shù)棧的作用主要用來存儲運(yùn)算結(jié)果以及運(yùn)算的操作數(shù),它不同于局部變量表通過索引來訪問,而是壓棧和出棧的方式
- e. 每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用是為了支持方法調(diào)用過程中的動態(tài)連接.動態(tài)鏈接就是將常量池中的符號引用在運(yùn)行期轉(zhuǎn)化為直接引用。
(3)本地方法棧:
本地方法棧和虛擬機(jī)棧類似,只不過本地方法棧為Native方法服務(wù)。
(4)堆:
java堆是所有線程所共享的一塊內(nèi)存,在虛擬機(jī)啟動時(shí)創(chuàng)建,幾乎所有的對象實(shí)例都在這里創(chuàng)建,因此該區(qū)域經(jīng)常發(fā)生垃圾回收操作。
(5)程序計(jì)數(shù)器:
內(nèi)存空間小,字節(jié)碼解釋器工作時(shí)通過改變這個(gè)計(jì)數(shù)值可以選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理和線程恢復(fù)等功能都需要依賴這個(gè)計(jì)數(shù)器完成。該內(nèi)存區(qū)域是唯一一個(gè)java虛擬機(jī)規(guī)范沒有規(guī)定任何OOM情況的區(qū)域。
2 heap 和stack 有什么區(qū)別
(1)申請方式
stack:由系統(tǒng)自動分配。例如,聲明在函數(shù)中一個(gè)局部變量 int b; 系統(tǒng)自動在棧中為 b 開辟空間
heap:需要程序員自己申請,并指明大小,在 c 中 malloc 函數(shù),對于Java 需要手動 new Object()的形式開辟
(2)申請后系統(tǒng)的響應(yīng)
stack:只要棧的剩余空間大于所申請空間,系統(tǒng)將為程序提供內(nèi)存,否則將報(bào)異常提示棧溢出。
heap:首先應(yīng)該知道操作系統(tǒng)有一個(gè)記錄空閑內(nèi)存地址的鏈表,當(dāng)系統(tǒng)收到程序的申請時(shí),會遍歷該鏈表,尋找第一個(gè)空間大于所申請空間的堆結(jié)點(diǎn),然后將該結(jié)點(diǎn)從空閑結(jié)點(diǎn)鏈表中刪除,并將該結(jié)點(diǎn)的空間分配給程序。另外,由于找到的堆結(jié)點(diǎn)的大小不一定正好等于申請的大小,系統(tǒng)會自動地將多余的那部分重新放入空閑鏈表中。
(3)申請大小的限制
stack:棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存的區(qū)域。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,在 WINDOWS 下,棧的大小是 2M(默認(rèn)值也取決于虛擬內(nèi)存的大?。?,如果申請的空間超過棧的剩余空間時(shí),將提示 overflow。因此,能從棧獲得的空間較小。
heap:堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來存儲的空閑內(nèi)存地址的, 自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。由此可見, 堆獲得的空間比較靈活,也比較大。
(4)申請效率的比較
stack:由系統(tǒng)自動分配,速度較快。但程序員是無法控制的。
heap:由 new 分配的內(nèi)存,一般速度比較慢,而且容易產(chǎn)生內(nèi)存碎片,不過用起來最方便。
(5)heap和stack中的存儲內(nèi)容
stack:在函數(shù)調(diào)用時(shí),第一個(gè)進(jìn)棧的是主函數(shù)中后的下一條指令(函數(shù)調(diào)用語句的下一條可執(zhí)行語句)的地址, 然后是函數(shù)的各個(gè)參數(shù),在大多數(shù)的 C 編譯器中,參數(shù)是由右往左入棧的,然后是函數(shù)中的局部變量。注意靜態(tài)變量是不入棧的。
當(dāng)本次函數(shù)調(diào)用結(jié)束后,局部變量先出棧,然后是參數(shù),最后棧頂指針指向最開始存的地址,也就是主函數(shù)中的下一條指令,程序由該點(diǎn)繼續(xù)運(yùn)行。
heap:一般是在堆的頭部用一個(gè)字節(jié)存放堆的大小。堆中的具體內(nèi)容由程序員安排。
3 java類加載過程?
Java類加載需要經(jīng)歷一下幾個(gè)過程:
(1)加載
加載時(shí)類加載的第一個(gè)過程,在這個(gè)階段,將完成一下三件事情:
- a. 通過一個(gè)類的全限定名獲取該類的二進(jìn)制流。
- b. 將該二進(jìn)制流中的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法去運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
- c. 在內(nèi)存中生成該類的Class對象,作為該類的數(shù)據(jù)訪問入口。
(2)鏈接
2.1)驗(yàn)證
驗(yàn)證的目的是為了確保Class文件的字節(jié)流中的信息不會危害到虛擬機(jī).在該階段主要完成以下四鐘驗(yàn)證:
- a. 文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件的規(guī)范,如主次版本號是否在當(dāng)前虛擬機(jī)范圍內(nèi),常量池中的常量是否有不被支持的類型.
- b. 元數(shù)據(jù)驗(yàn)證:對字節(jié)碼描述的信息進(jìn)行語義分析,如這個(gè)類是否有父類,是否繼承了不被繼承的類等。
- c. 字節(jié)碼驗(yàn)證:是整個(gè)驗(yàn)證過程中最復(fù)雜的一個(gè)階段,通過驗(yàn)證數(shù)據(jù)流和控制流的分析,確定程序語義是否正確,主要針對方法體的驗(yàn)證。如:方法中的類型轉(zhuǎn)換是否正確,跳轉(zhuǎn)指令是否正確等。
- d. 符號引用驗(yàn)證:這個(gè)動作在后面的解析過程中發(fā)生,主要是為了確保解析動作能正確執(zhí)行。
2.2)準(zhǔn)備
準(zhǔn)備階段是為類的靜態(tài)變量分配內(nèi)存并將其初始化為默認(rèn)值,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配。準(zhǔn)備階段不分配類中的實(shí)例變量的內(nèi)存,實(shí)例變量將會在對象實(shí)例化時(shí)隨著對象一起分配在Java堆中。
2.3)解析
該階段主要完成符號引用到直接引用的轉(zhuǎn)換動作。解析動作并不一定在初始化動作完成之前,也有可能在初始化之后。
(3)初始化 初始化時(shí)類加載的最后一步,前面的類加載過程,除了在加載階段用戶應(yīng)用程序可以通過自定義類加載器參與之外,其余動作完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段,才真正開始執(zhí)行類中定義的Java程序代碼。
4 什么是類加載器,類加載器有哪些?
實(shí)現(xiàn)通過類的全限定名獲取該類的二進(jìn)制字節(jié)流的代碼塊叫做類加載器。
主要有以下四種類加載器:
(1)啟動類加載器(Bootstrap ClassLoader)用來加載java核心類庫,無法被java程序直接引用。
(2)擴(kuò)展類加載器(extensions class loader):它用來加載 Java 的擴(kuò)展庫。Java 虛擬機(jī)的實(shí)現(xiàn)會提供一個(gè)擴(kuò)展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。
(3)系統(tǒng)類加載器(system class loader)也叫應(yīng)用類加載器:它根據(jù) Java 應(yīng)用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應(yīng)用的類都是由它來完成加載的??梢酝ㄟ^ ClassLoader.getSystemClassLoader()來獲取它。
(4)用戶自定義類加載器,通過繼承 java.lang.ClassLoader類的方式實(shí)現(xiàn)。
5 java中垃圾收集的方法有哪些?
1)引用計(jì)數(shù)法算法 應(yīng)用于:微軟的COM/ActionScrip3/Python等
a) 如果對象沒有被引用,就會被回收,缺點(diǎn):需要維護(hù)一個(gè)引用計(jì)算器
2)可達(dá)性分析算法 以根對象集合(GC Roots)為起始點(diǎn),按照從上至下的方式搜索被根對象集合所連接的目標(biāo)對象是否可達(dá)。不可達(dá)的,就意味著該對象已經(jīng)死亡,可以標(biāo)記為垃圾對象。
3)復(fù)制算法 年輕代中使用的是Minor GC,這種GC算法采用的是復(fù)制算法(Copying)
a) 效率高,缺點(diǎn):需要內(nèi)存容量大,比較耗內(nèi)存
b) 使用在占空間比較小、刷新次數(shù)多的新生區(qū)
4)標(biāo)記-清除算法 老年代一般是由標(biāo)記清除或者是標(biāo)記清除與標(biāo)記整理的混合實(shí)現(xiàn)
a) 效率比較低,會產(chǎn)生碎片。
5)標(biāo)記-壓縮算法 老年代一般是由標(biāo)記清除或者是標(biāo)記清除與標(biāo)記整理的混合實(shí)現(xiàn)
a) 效率低速度慢,需要移動對象,但不會產(chǎn)生碎片。
6)標(biāo)記-清除-壓縮算法 標(biāo)記清除-標(biāo)記壓縮的集合,多次GC后才Compact
a) 適用于占空間大刷新次數(shù)少的養(yǎng)老區(qū),是4)和5)的集合體
6 如何判斷一個(gè)對象是否存活?(或者GC對象的判定方法)
判斷一個(gè)對象是否存活有兩種方法:
(1)引用計(jì)數(shù)法
所謂引用計(jì)數(shù)法就是給每一個(gè)對象設(shè)置一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用這個(gè)對象時(shí),就將計(jì)數(shù)器加一,引用失效時(shí),計(jì)數(shù)器就減一。當(dāng)一個(gè)對象的引用計(jì)數(shù)器為零時(shí),說明此對象沒有被引用,也就是“死對象”,將會被垃圾回收.
引用計(jì)數(shù)法有一個(gè)缺陷就是無法解決循環(huán)引用問題,也就是說當(dāng)對象A引用對象B,對象B又引用者對象A,那么此時(shí)A,B對象的引用計(jì)數(shù)器都不為零,也就造成無法完成垃圾回收,所以主流的虛擬機(jī)都沒有采用這種算法。
(2)可達(dá)性算法(引用鏈法)
該算法的基本思路就是通過以GC Roots對象作為起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,搜索走過的路徑被稱為引用鏈(Reference Chain),當(dāng)一個(gè)對象到GC Roots沒有任何引用鏈相連時(shí)(即從GC Roots節(jié)點(diǎn)到該節(jié)點(diǎn)不可達(dá)),則證明該對象是不可用的。
在java中可以作為GC Roots的對象有以下幾種:虛擬機(jī)棧中引用的對象、方法區(qū)類靜態(tài)屬性引用的對象、方法區(qū)常量池引用的對象、本地方法棧JNI引用的對象。
7 簡述java內(nèi)存分配與回收策略以及Minor GC和Major GC(full GC)
內(nèi)存分配:
(1)棧區(qū):棧分為java虛擬機(jī)棧和本地方法棧
(2)堆區(qū):堆被所有線程共享區(qū)域,在虛擬機(jī)啟動時(shí)創(chuàng)建,唯一目的存放對象實(shí)例。堆區(qū)是gc的主要區(qū)域,通常情況下分為兩個(gè)區(qū)塊年輕代和年老代。更細(xì)一點(diǎn)年輕代又分為Eden區(qū),主要放新創(chuàng)建對象,F(xiàn)rom survivor 和 To survivor 保存gc后幸存下的對象,默認(rèn)情況下各自占比 8:1:1。
(3)方法區(qū):被所有線程共享區(qū)域,用于存放已被虛擬機(jī)加載的類信息,常量,靜態(tài)變量等數(shù)據(jù)。被Java虛擬機(jī)描述為堆的一個(gè)邏輯部分。習(xí)慣上也叫它永久代(permanment generation)
(4)程序計(jì)數(shù)器:當(dāng)前線程所執(zhí)行的信號指示器。通過改變計(jì)數(shù)器的值來確定下一條指令,比如循環(huán),分支,跳轉(zhuǎn),異常處理,線程恢復(fù)等都是依賴計(jì)數(shù)器來完成。線程私有的。
回收策略以及Minor GC和Major GC:
(1)對象優(yōu)先在堆的Eden區(qū)分配。
(2)大對象直接進(jìn)入老年代。
(3)長期存活的對象將直接進(jìn)入老年代。
當(dāng)Eden區(qū)沒有足夠的空間進(jìn)行分配時(shí),虛擬機(jī)會執(zhí)行一次Minor GC.Minor GC通常發(fā)生在新生代的Eden區(qū),在這個(gè)區(qū)的對象生存期短,往往發(fā)生GC的頻率較高,回收速度比較快;Full Gc/Major GC 發(fā)生在老年代,一般情況下,觸發(fā)老年代GC的時(shí)候不會觸發(fā)Minor GC,但是通過配置,可以在Full GC之前進(jìn)行一次Minor GC這樣可以加快老年代的回收速度。
本文鏈接:
本文章“JVM相關(guān)技術(shù)面試題匯總”已幫助 230 人
免責(zé)聲明:本信息由用戶發(fā)布,本站不承擔(dān)本信息引起的任何交易及知識產(chǎn)權(quán)侵權(quán)的法律責(zé)任!
本文由賦能網(wǎng) 整理發(fā)布。了解更多培訓(xùn)機(jī)構(gòu)》培訓(xùn)課程》學(xué)習(xí)資訊》課程優(yōu)惠》課程開班》學(xué)校地址等機(jī)構(gòu)信息,可以留下您的聯(lián)系方式,讓課程老師跟你詳細(xì)解答:
咨詢熱線:4008-569-579