JVM
基于JDK8
1. 說一說JVM的主要組成部分
- 方法區(qū)和堆是所有線程共享的內(nèi)存區(qū)域;而虛擬機棧、本地方法棧和程序計數(shù)器的運行是線程私有的內(nèi)存區(qū)域,運行時數(shù)據(jù)區(qū)域就是我們常說的JVM的內(nèi)存。
- 類加載子系統(tǒng):根據(jù)給定的全限定名類名(如:java.lang.Object)來裝載class文件到運行時數(shù)據(jù)區(qū)中的方法區(qū)中。
- Java堆是Java虛擬機所管理的內(nèi)存中最大的一塊,也是垃圾回收的主要區(qū)域。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內(nèi)存。
- 方法區(qū)與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域,它用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。
- 程序計數(shù)器是一塊較小的內(nèi)存空間,它的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器,用來指示執(zhí)行引擎下一條執(zhí)行指令的地址。
- Java虛擬機棧也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、返回方法地址等信息。每一個方法被調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機棧中從入棧到出棧的過程。
- 本地方法棧(Native Method Stacks),本地方法棧(Native Method Stacks)與虛擬機棧所發(fā)揮的作用是非常相似的,其區(qū)別不過是虛擬機棧為虛擬機執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機使用到的Native方法服務(wù)。
- 執(zhí)行引擎:根據(jù)程序計數(shù)器中存儲的指令地址執(zhí)行classes中的指令。
- 本地接口:與本地方法庫交互,是其它編程語言交互的接口。
2. 說一下 JVM 的作用?
首先通過編譯器把 Java 代碼轉(zhuǎn)換成字節(jié)碼,類加載器(ClassLoader)再把字節(jié)碼加載到內(nèi)存中,將其放在運行時數(shù)據(jù)區(qū)(Runtime data area)的方法區(qū)內(nèi),而字節(jié)碼文件只是 JVM 的一套指令集規(guī)范,并不能直接交給底層操作系統(tǒng)去執(zhí)行,因此需要特定的命令解析器執(zhí)行引擎(Execution Engine),將字節(jié)碼翻譯成底層系統(tǒng)指令,再交由 CPU 去執(zhí)行,而這個過程中需要調(diào)用其他語言的本地庫接口(Native Interface)來實現(xiàn)整個程序的功能。
3. 說一下堆棧的區(qū)別?
物理地址
堆的物理地址分配對象是不連續(xù)的。因此性能慢些。在GC的時候也要考慮到不連續(xù)的分配,所以有各種算法。比如,標(biāo)記-消除,復(fù)制,標(biāo)記-壓縮,分代(即新生代使用復(fù)制算法,老年代使用標(biāo)記——壓縮)
棧使用的是數(shù)據(jù)結(jié)構(gòu)中的棧,先進后出的原則,物理地址分配是連續(xù)的。所以性能快。
內(nèi)存分別
堆因為是不連續(xù)的,所以分配的內(nèi)存是在運行期確認(rèn)的,因此大小不固定。一般堆大小遠(yuǎn)遠(yuǎn)大于棧。
棧是連續(xù)的,所以分配的內(nèi)存大小要在編譯期就確認(rèn),大小是固定的。
存放的內(nèi)容
堆存放的是對象的實例和數(shù)組。因此該區(qū)更關(guān)注的是數(shù)據(jù)的存儲
棧存放:局部變量,操作數(shù)棧,返回結(jié)果。該區(qū)更關(guān)注的是程序方法的執(zhí)行。
PS:
靜態(tài)變量放在方法區(qū) 靜態(tài)的對象還是放在堆。 程序的可見度
堆對于整個應(yīng)用程序都是共享、可見的。
棧只對于線程是可見的。所以也是線程私有。他的生命周期和線程相同。
4. Java內(nèi)存泄漏
內(nèi)存泄漏是指不再被使用的對象或者變量一直被占據(jù)在內(nèi)存中。
嚴(yán)格來說,只有對象不會再被程序用到了,但是GC又不能回收他們的情況,才叫內(nèi)存泄漏。
理論上來說,Java是有GC垃圾回收機制的,也就是說,不再被使用的對象,會被GC自動回收掉,自動從內(nèi)存中清除。
但是,即使這樣,Java也還是存在著內(nèi)存泄漏的情況,java導(dǎo)致內(nèi)存泄露的原因很明確:長生命周期的對象持有短生命周期對象的引用就很可能發(fā)生內(nèi)存泄露,盡管短生命周期對象已經(jīng)不再需要,但是因為長生命周期對象持有它的引用而導(dǎo)致不能被回收,這就是java中內(nèi)存泄露的發(fā)生場景。
5. JVM 有哪些垃圾回收算法?
- 標(biāo)記-清除算法:標(biāo)記有用對象,然后進行清除回收。缺點:效率不高,無法清除垃圾碎片。
- 復(fù)制算法:按照容量劃分二個大小相等的內(nèi)存區(qū)域,當(dāng)一塊用完的時候?qū)⒒钪膶ο髲?fù)制到另一塊上,然后再把已使用的內(nèi)存空間一次清理掉。缺點:內(nèi)存使用率不高,只有原來的一半,消耗內(nèi)存。
- 標(biāo)記-整理算法:標(biāo)記無用對象,讓所有存活的對象都向一端移動,然后直接清除掉端邊界以外的內(nèi)存。
- 分代算法:根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊,一般是新生代和老年代,新生代基本采用復(fù)制算法,老年代采用標(biāo)記整理算法。
6. 說一下 JVM 有哪些垃圾回收器?
7. 說一下類加載的執(zhí)行過程?
- 加載:根據(jù)查找路徑找到相應(yīng)的 class 文件然后裝載入內(nèi)存中;
- 驗證:檢查加載的 class 文件的正確性;
- 準(zhǔn)備:給類中的靜態(tài)變量分配內(nèi)存空間;
- 解析:虛擬機將常量池中的符號引用替換成直接引用的過程。符號引用就理解為一個標(biāo)示,而在直接引用直接指向內(nèi)存中的地址;
- 初始化:對靜態(tài)變量和靜態(tài)代碼塊執(zhí)行初始化工作。
8. 什么是雙親委派模型?為什么要使用雙親委派模型?
什么是雙親委派模型
- 當(dāng)需要加載一個類的時候,子類加載器并不會馬上去加載,而是依次去請求父類加載器加載
- 如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終將到達(dá)頂層的啟動類加載器;
- 如果父類加載器可以完成類加載任務(wù),就成功返回,倘若父類加載器無法完成此加載任務(wù),子加載器才會嘗試自己去加載,這就是雙親委派模式。
為什么要使用雙親委派模型
可以防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼,如果沒有雙親委派模型而是由各個類加載器自行加載的話,如果用戶編寫了一個java.lang.Object的同名類并放在ClassPath中,多個類加載器都去加載這個類到內(nèi)存中,系統(tǒng)中將會出現(xiàn)多個不同的Object類,那么類之間的比較結(jié)果及類的唯一性將無法保證,而且如果不使用這種雙親委派模型將會給虛擬機的安全帶來隱患。所以,要讓類對象進行比較有意義,前提是他們要被同一個類加載器加載。
9. CMS垃圾清理的過程
CMS整個過程比之前的收集器要復(fù)雜,整個過程分為4個主要階段,即初始標(biāo)記階段、并發(fā)標(biāo)記階段、重新標(biāo)記階段和并發(fā)清除階段。(涉及STW的階段主要是:初始標(biāo)記 和 重新標(biāo)記 stop-the-world)
- 初始標(biāo)記(Initial-Mark)階段:在這個階段中,程序中所有的工作線程都將會因為“stop-the-world”機制而出現(xiàn)短暫的暫停,這個階段的主要任務(wù)僅僅只是標(biāo)記出 GC Roots 能直接關(guān)聯(lián)到的對象。一旦標(biāo)記完成之后就會恢復(fù)之前被暫停的所有應(yīng)用線程。由于直接關(guān)聯(lián)對象比較小,所以這里的速度非??臁?/li>
- 并發(fā)標(biāo)記(Concurrent-Mark)階段:從 Gc Roots 的直接關(guān)聯(lián)對象開始遍歷整個對象圖的過程,這個過程耗時較長但是不需要停頓用戶線程,可以與垃圾收集線程一起并發(fā)運行。
- 重新標(biāo)記(Remark)階段:由于在并發(fā)標(biāo)記階段中,程序的工作線程會和垃圾收集線程同時運行或者交叉運行,因此為了修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄,這個階段的停頓時間通常會比初始標(biāo)記階段稍長一些,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時間短。
- 并發(fā)清除(Concurrent-Sweep)階段:此階段清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對象,釋放內(nèi)存空間。由于不需要移動存活對象,所以這個階段也是可以與用戶線程同時并發(fā)的
10. 常用的 JVM 調(diào)優(yōu)的參數(shù)都有哪些?
- -XX:NewRatio=4:設(shè)置年輕的和老年代的內(nèi)存比例為 1:4;
- -XX:SurvivorRatio=8:設(shè)置新生代 Eden 和 Survivor 比例為 8:2;
- –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;
- -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;
- -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;
- -XX:+PrintGC:開啟打印 gc 信息;
- -XX:+PrintGCDetails:打印 gc 詳細(xì)信息。