每日面試之JVM
01
說(shuō)一說(shuō)你了解的Java內(nèi)存區(qū)域 ?
參考答案 Java 虛擬機(jī)在執(zhí)行 Java 程序的過(guò)程中會(huì)把他所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域。
Java 虛擬機(jī)規(guī)范將 JVM 所管理的內(nèi)存分為以下幾個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū):
程序計(jì)數(shù)器(線程私有)
Java 虛擬機(jī)棧(線程私有)
本地方法棧
Java 堆(線程共有)
方法區(qū)(線程共有)
作用:
? ? 程序計(jì)數(shù)器:對(duì)于單線程,沒(méi)有程序計(jì)數(shù)器都可以,但是實(shí)際情況是多線程環(huán)境的,由于操作系統(tǒng)是時(shí)間片輪轉(zhuǎn)調(diào)度的,某個(gè)線程還沒(méi)執(zhí)行完時(shí)間片到了,輪到下一個(gè)線程執(zhí)行,這時(shí)程序計(jì)數(shù)器會(huì)記錄該線程代碼執(zhí)行的位置,等到下次輪到該線程執(zhí)行時(shí)就會(huì)從該位置繼續(xù)往下執(zhí)行。
??? Java虛擬機(jī)棧:棧是運(yùn)行時(shí)創(chuàng)建的,是線程私有的,生命周期與線程相同,存儲(chǔ)聲明的變量,每個(gè)棧由多個(gè)棧幀組成,對(duì)應(yīng)線程調(diào)用方法時(shí)占用的內(nèi)存。
????
????本地方法棧:為 native 方法服務(wù),native 方法是一種由非 java 語(yǔ)言實(shí)現(xiàn)的 java 方法,與 java 環(huán)境外交互,如可以用本地方法與操作系統(tǒng)交互。
????堆:堆是所有線程共享的一塊內(nèi)存,是在 java 虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建的,幾乎所有對(duì)象實(shí)例都在此創(chuàng)建,所以經(jīng)常發(fā)生垃圾回收操作
???方法區(qū):主要存儲(chǔ)已被虛擬機(jī)加載的類的信息、常量、靜態(tài)變量和即時(shí)編譯器編譯后的代碼等數(shù)據(jù),該區(qū)域是被線程共享的,很少發(fā)生垃圾回收
JDK8 之前,Hotspot 中方法區(qū)的實(shí)現(xiàn)是永久代(Perm)
JDK8 開(kāi)始使用元空間(Metaspace),以前永久代所有內(nèi)容的字符串常量移至堆內(nèi)存,其他內(nèi)容移至元空間,元空間直接在本地內(nèi)存分配。
02
JDK8為什么要使用元空間取代永久代???
參考答案
原因如下:
字符串存在永久代中,容易出現(xiàn)性能問(wèn)題和內(nèi)存溢出 類及方法的信息等比較難確定其大小,因此對(duì)于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出 永久代會(huì)為 GC 帶來(lái)不必要的復(fù)雜度,并且回收效率偏低 更安全:相對(duì)于C++來(lái)說(shuō),java語(yǔ)言更加安全,jvm具有自動(dòng)的垃圾回收機(jī)制,而C++的程序員需要手動(dòng)去清除垃圾。JVM這種設(shè)計(jì)思路確實(shí)使java語(yǔ)言的安全性提高了,并且不需要程序員手動(dòng)去清理垃圾。
03
簡(jiǎn)單介紹下Java中垃圾回收機(jī)制??
參考答案 什么樣的對(duì)象會(huì)被當(dāng)做垃圾回收? 引用計(jì)數(shù)法:指的是如果某個(gè)地方引用了這個(gè)對(duì)象就+1,如果失效了就-1,當(dāng)為 0 就會(huì)回收但是 JVM 沒(méi)有用這種方式,因?yàn)闊o(wú)法判定相互循環(huán)引用(A 引用 B,B 引用 A)的情況。
引用鏈法: 通過(guò)一種 GC ROOT 的對(duì)象(方法區(qū)中靜態(tài)變量引用的對(duì)象等-static 變量)來(lái)判斷,如果有一條鏈能夠到達(dá) GC ROOT 就說(shuō)明,不能到達(dá) GC ROOT 就說(shuō)明可以回收
如何檢驗(yàn)對(duì)象是否被回收?
????可以重寫(xiě) Object 類中的 finalize 方法,這個(gè)方法在垃圾收集器執(zhí)行的時(shí)候,被收集器自動(dòng)調(diào)用執(zhí)行的。怎樣通知垃圾收集器回收對(duì)象?
????可以調(diào)用 System 類的靜態(tài)方法 gc(),通知垃圾收集器去清理垃圾,但不能保證收集動(dòng)作立即執(zhí)行,具體的執(zhí)行時(shí)間取決于垃圾收集的算法。
04
Java中類加載過(guò)程是什么樣的 ?
參考答案 類加載的步驟為,加載 -> 驗(yàn)證 -> 準(zhǔn)備 -> 解析 -> 初始化。
1、加載:
獲取類的二進(jìn)制字節(jié)流
將字節(jié)流代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
在堆中生成class字節(jié)碼對(duì)象
2、驗(yàn)證:連接過(guò)程的第一步,確保 class 文件的字節(jié)流中的信息符合當(dāng)前 JVM 的要求,不會(huì)危害 JVM 的安全
3、準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存并將其初始化為默認(rèn)值
4、解析:JVM 將常量池內(nèi)符號(hào)引用替換成直接引用的過(guò)程
5、初始化:執(zhí)行類構(gòu)造器的初始化的過(guò)程
05
堆里面的分區(qū):Eden,survival(from+ to),老年代,各自的特點(diǎn) ?
參考答案
06
有哪幾種GC 的收集算法?說(shuō)說(shuō)各自的原理與特點(diǎn)?
參考答案
07
GC 收集器有哪些?各有什么特點(diǎn) ?
參考答案
串行垃圾回收器
Serial收集器(復(fù)制算法)+SerialOld收集器(標(biāo)記整理算法)
吞吐量?jī)?yōu)先回收器
Parallel Scaverge收集器(復(fù)制算法)+Parallel Old收集器(是Parallel Scavenge收集器的老年代版本,標(biāo)記整理算法)
響應(yīng)時(shí)間優(yōu)先垃圾收集器
ParNew收集器(Serial收集器的多線程版本,復(fù)制算法)+CMS收集器(標(biāo)記清除)
08
什么是類加載器,類加載器有哪些 ?
參考答案 實(shí)現(xiàn)通過(guò)類的權(quán)限定名獲取該類的二進(jìn)制字節(jié)流的代碼塊叫做類加載器。
主要有一下四種類加載器:
1. 啟動(dòng)類加載器(Bootstrap ClassLoader)用來(lái)加載 java 核心類庫(kù),無(wú)法被 java 程序直接引用。
2. 擴(kuò)展類加載器(extensions class loader):它用來(lái)加載 Java 的擴(kuò)展庫(kù)。Java 虛擬機(jī)的實(shí)現(xiàn)會(huì)提供一個(gè)擴(kuò)展庫(kù)目錄。該類加載器在此目錄里面查找并加載 Java 類。
3. 系統(tǒng)類加載器(system class loader):它根據(jù) Java 應(yīng)用的類路徑(CLASSPATH)來(lái)加載 Java 類。一般來(lái)說(shuō),Java 應(yīng)用的類都是由它來(lái)完成加載的。可以通過(guò)ClassLoader.getSystemClassLoader()來(lái)獲取它。
4. 用戶自定義類加載器,通過(guò)繼承 java.lang.ClassLoader 類的方式實(shí)現(xiàn)。
09
方法區(qū)內(nèi)存溢出怎么處理??
參考答案
在 Java 虛擬機(jī)中,方法區(qū)是可供各線程共享的運(yùn)行時(shí)內(nèi)存區(qū)域。 在不同的 JDK 版本中,方法區(qū)中存儲(chǔ)的數(shù)據(jù)是不一樣的:
JDK 1.7 之前的版本,運(yùn)行時(shí)常量池是方法區(qū)的一個(gè)部分,同時(shí)方法區(qū)里面存儲(chǔ)了類的元數(shù)據(jù)信息、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等。 JDK 1.7 開(kāi)始,JVM 已經(jīng)將運(yùn)行時(shí)常量池從方法區(qū)中移了出來(lái),在 JVM 開(kāi)辟了一塊區(qū)域存放常量池。 永久代就是 HotSpot 版的 Java 虛擬機(jī)對(duì)虛擬機(jī)規(guī)范中方法區(qū)的一種實(shí)現(xiàn)方式,永久代和方法區(qū)的關(guān)系就像 Java 中類和接口的關(guān)系。 HotSpot 版的 Java 虛擬機(jī)在 JDK 1.8 之后取消了永久代,改為元空間,類的元信息被存儲(chǔ)在元空間中。元空間沒(méi)有使用堆內(nèi)存,而是與堆不相連的本地內(nèi)存區(qū)域。所以,理論上系統(tǒng)可以使用的內(nèi)存有多大,元空間就有多大,所以不會(huì)出現(xiàn)永久代存在時(shí)的內(nèi)存溢出問(wèn)題。 在 jdk 1.7 及之前版本出現(xiàn)內(nèi)存溢出的處理辦法:
檢查代碼中是否出現(xiàn)死循環(huán)
是否創(chuàng)建了過(guò)多或過(guò)大的對(duì)象
是否出現(xiàn)死鎖現(xiàn)象
提高 jvm 堆內(nèi)存大小的配置
10
對(duì)象創(chuàng)建過(guò)程是什么樣的??
參考答案 對(duì)象在 JVM 中的創(chuàng)建過(guò)程如下:
JVM 會(huì)先去方法區(qū)找有沒(méi)有所創(chuàng)建對(duì)象的類存在,有就可以創(chuàng)建對(duì)象了,沒(méi)有則把該類加載到方法區(qū)
在創(chuàng)建類的對(duì)象時(shí),首先會(huì)先去堆內(nèi)存中分配空間
當(dāng)空間分配完后,加載對(duì)象中所有的非靜態(tài)成員變量到該空間下
所有的非靜態(tài)成員變量加載完成之后,對(duì)所有的非靜態(tài)成員進(jìn)行默認(rèn)初始化
所有的非靜態(tài)成員默認(rèn)初始化完成之后,調(diào)用相應(yīng)的構(gòu)造方法到棧中
在棧中執(zhí)行構(gòu)造函數(shù)時(shí),先執(zhí)行隱式,再執(zhí)行構(gòu)造方法中書(shū)寫(xiě)的代碼
執(zhí)行順序:靜態(tài)代碼庫(kù),構(gòu)造代碼塊,構(gòu)造方法
當(dāng)整個(gè)構(gòu)造方法全部執(zhí)行完,此對(duì)象創(chuàng)建完成,并把堆內(nèi)存中分配的空間地址賦給對(duì)象名
4.中間件等
更多信息請(qǐng)關(guān)注公眾號(hào):「軟件老王」,關(guān)注不迷路,軟件老王和他的IT朋友們,分享一些他們的技術(shù)見(jiàn)解和生活故事。














