您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)JVM的面試真題有哪些,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
JVM系列知識(shí),非常的重要,面試基本是必問的。
「對(duì)象一定分配在堆中嗎?」 不一定的,JVM通過**「逃逸分析」**,那些逃不出方法的對(duì)象會(huì)在棧上分配。
「什么是逃逸分析?」
逃逸分析(Escape Analysis),是一種可以有效減少Java 程序中同步負(fù)載和內(nèi)存堆分配壓力的跨函數(shù)全局?jǐn)?shù)據(jù)流分析算法。通過逃逸分析,Java Hotspot編譯器能夠分析出一個(gè)新的對(duì)象的引用的使用范圍,從而決定是否要將這個(gè)對(duì)象分配到堆上。
? 逃逸分析是指分析指針動(dòng)態(tài)范圍的方法,它同編譯器優(yōu)化原理的指針分析和外形分析相關(guān)聯(lián)。當(dāng)變量(或者對(duì)象)在方法中分配后,其指針有可能被返回或者被全局引用,這樣就會(huì)被其他方法或者線程所引用,這種現(xiàn)象稱作指針(或者引用)的逃逸(Escape)。通俗點(diǎn)講,如果一個(gè)對(duì)象的指針被多個(gè)方法或者線程引用時(shí),那么我們就稱這個(gè)對(duì)象的指針發(fā)生了逃逸。 ?
「一個(gè)逃逸分析的例子」
/** * @author 撿田螺的小男孩 */ public class EscapeAnalysisTest { public static Object object; //StringBuilder可能被其他方法改變,逃逸到了方法外部。 public StringBuilder escape(String a, String b) { StringBuilder str = new StringBuilder(); str.append(a); str.append(b); return str; } //不直接返回StringBuffer,不發(fā)生逃逸 public String notEscape(String a, String b) { StringBuilder str = new StringBuilder(); str.append(a); str.append(b); return str.toString(); } //外部線程可見object,發(fā)生逃逸 public void objectEscape(){ object = new Object(); } //僅方法內(nèi)部可見,不發(fā)生逃逸 public void objectNotEscape(){ Object object = new Object(); } }
「逃逸分析的好處」
?棧上分配,可以降低垃圾收集器運(yùn)行的頻率。同步消除,如果發(fā)現(xiàn)某個(gè)對(duì)象只能從一個(gè)線程可訪問,那么在這個(gè)對(duì)象上的操作可以不需要同步。標(biāo)量替換,把對(duì)象分解成一個(gè)個(gè)基本類型,并且內(nèi)存分配不再是分配在堆上,而是分配在棧上。這樣的好處有,一、減少內(nèi)存使用,因?yàn)椴挥蒙蓪?duì)象頭。二、程序內(nèi)存回收效率高,并且GC頻率也會(huì)減少。?
「什么是元空間?什么是永久代?為什么用元空間代替永久代?」 我們先回顧一下**「方法區(qū)」**吧,看看虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)內(nèi)存圖,如下:
? 方法區(qū)和堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯后的代碼等數(shù)據(jù)。 ?
「什么是永久代?它和方法區(qū)有什么關(guān)系呢?」
? 如果在HotSpot虛擬機(jī)上開發(fā)、部署,很多程序員都把方法區(qū)稱作永久代。可以說方法區(qū)是規(guī)范,永久代是Hotspot針對(duì)該規(guī)范進(jìn)行的實(shí)現(xiàn)。在Java7及以前的版本,方法區(qū)都是永久代實(shí)現(xiàn)的。 ?
「什么是元空間?它和方法區(qū)有什么關(guān)系呢?」
? 對(duì)于Java8,HotSpots取消了永久代,取而代之的是元空間(Metaspace)。換句話說,就是方法區(qū)還是在的,只是實(shí)現(xiàn)變了,從永久代變?yōu)樵臻g了。 ?
「為什么使用元空間替換了永久代?」
永久代的方法區(qū),和堆使用的物理內(nèi)存是連續(xù)的。
**「永久代」**是通過以下這兩個(gè)參數(shù)配置大小的~
-XX:PremSize:設(shè)置永久代的初始大小
-XX:MaxPermSize: 設(shè)置永久代的最大值,默認(rèn)是64M
對(duì)于**「永久代」,如果動(dòng)態(tài)生成很多class的話,就很可能出現(xiàn)「java.lang.OutOfMemoryError: PermGen space錯(cuò)誤」**,因?yàn)橛谰么臻g配置有限嘛。最典型的場(chǎng)景是,在web開發(fā)比較多jsp頁面的時(shí)候。
JDK8之后,方法區(qū)存在于元空間(Metaspace)。物理內(nèi)存不再與堆連續(xù),而是直接存在于本地內(nèi)存中,理論上機(jī)器**「內(nèi)存有多大,元空間就有多大」**。
可以通過以下的參數(shù)來設(shè)置元空間的大?。?/p>
?-XX:MetaspaceSize,初始空間大小,達(dá)到該值就會(huì)觸發(fā)垃圾收集進(jìn)行類型卸載,同時(shí)GC會(huì)對(duì)該值進(jìn)行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值;如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時(shí),適當(dāng)提高該值。-XX:MaxMetaspaceSize,最大空間,默認(rèn)是沒有限制的。-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空間容量的百分比,減少為分配空間所導(dǎo)致的垃圾收集-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空間容量的百分比,減少為釋放空間所導(dǎo)致的垃圾收集?
「所以,為什么使用元空間替換永久代?」
? 表面上看是為了避免OOM異常。因?yàn)橥ǔJ褂肞ermSize和MaxPermSize設(shè)置永久代的大小就決定了永久代的上限,但是不是總能知道應(yīng)該設(shè)置為多大合適, 如果使用默認(rèn)值很容易遇到OOM錯(cuò)誤。當(dāng)使用元空間時(shí),可以加載多少類的元數(shù)據(jù)就不再由MaxPermSize控制, 而由系統(tǒng)的實(shí)際可用空間來控制啦。 ?
進(jìn)行垃圾回收的過程中,會(huì)涉及對(duì)象的移動(dòng)。為了保證對(duì)象引用更新的正確性,必須暫停所有的用戶線程,像這樣的停頓,虛擬機(jī)設(shè)計(jì)者形象描述為**「Stop The World」**。
在HotSpot中,有個(gè)數(shù)據(jù)結(jié)構(gòu)(映射表)稱為**「OopMap」。一旦類加載動(dòng)作完成的時(shí)候,HotSpot就會(huì)把對(duì)象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計(jì)算出來,記錄到OopMap。在即時(shí)編譯過程中,也會(huì)在「特定的位置」**生成 OopMap,記錄下棧上和寄存器里哪些位置是引用。
這些特定的位置主要在:
1.循環(huán)的末尾(非 counted 循環(huán))
2.方法臨返回前 / 調(diào)用方法的call指令后
3.可能拋異常的位置
這些位置就叫作**「安全點(diǎn)(safepoint)?!?* 用戶程序執(zhí)行時(shí)并非在代碼指令流的任意位置都能夠在停頓下來開始垃圾收集,而是必須是執(zhí)行到安全點(diǎn)才能夠暫停。
JVM包含兩個(gè)子系統(tǒng)和兩個(gè)組件,分別為
?Class loader(類裝載子系統(tǒng))Execution engine(執(zhí)行引擎子系統(tǒng));Runtime data area(運(yùn)行時(shí)數(shù)據(jù)區(qū)組件)Native Interface(本地接口組件)。?
「Class loader(類裝載):」 根據(jù)給定的全限定名類名(如:java.lang.Object)來裝載class文件到運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)中。
「Execution engine(執(zhí)行引擎)」:執(zhí)行class的指令。
「Native Interface(本地接口):」 與native lib交互,是其它編程語言交互的接口。
「Runtime data area(運(yùn)行時(shí)數(shù)據(jù)區(qū)域)」:即我們常說的JVM的內(nèi)存。
? 首先通過編譯器把 Java源代碼轉(zhuǎn)換成字節(jié)碼,Class loader(類裝載)再把字節(jié)碼加載到內(nèi)存中,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),而字節(jié)碼文件只是 JVM 的一套指令集規(guī)范,并不能直接交給底層操作系統(tǒng)去執(zhí)行,因此需要特定的命令解析器執(zhí)行引擎(Execution Engine),將字節(jié)碼翻譯成底層系統(tǒng)指令,再交由 CPU 去執(zhí)行,而這個(gè)過程中需要調(diào)用其他語言的本地庫接口(Native Interface)來實(shí)現(xiàn)整個(gè)程序的功能。 ?
「守護(hù)線程」是區(qū)別于用戶線程哈,「用戶線程」即我們手動(dòng)創(chuàng)建的線程,而守護(hù)線程是程序運(yùn)行的時(shí)候在后臺(tái)提供一種「通用服務(wù)的線程」。垃圾回收線程就是典型的守護(hù)線程。
「守護(hù)線程和非守護(hù)線程的區(qū)別是?」 我們通過例子來看吧~
/** */ public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()-> { while (true) { try { Thread.sleep(1000); System.out.println("我是子線程(用戶線程.I am running"); } catch (Exception e) { } } }); //標(biāo)記為守護(hù)線程 t1.setDaemon(true); //啟動(dòng)線程 t1.start(); Thread.sleep(3000); System.out.println("主線程執(zhí)行完畢..."); }
運(yùn)行結(jié)果:
可以發(fā)現(xiàn)標(biāo)記為守護(hù)線程后,「主線程銷毀停止,守護(hù)線程一起銷毀」。我們?cè)倏聪?,去?t1.setDaemon(true)守護(hù)標(biāo)記的效果:
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()-> { while (true) { try { Thread.sleep(1000); System.out.println("我是子線程(用戶線程.I am running"); } catch (Exception e) { } } }); //啟動(dòng)線程 t1.start(); Thread.sleep(3000); System.out.println("主線程執(zhí)行完畢..."); }
所以,當(dāng)主線程退出時(shí),JVM 也跟著退出運(yùn)行,守護(hù)線程同時(shí)也會(huì)被回收,即使是死循環(huán)。如果是用戶線程,它會(huì)一直停在死循環(huán)跑。這就是**「守護(hù)線程和非守護(hù)線程的區(qū)別」**啦。
守護(hù)線程擁有**「自動(dòng)結(jié)束自己生命周期的特性」,非守護(hù)線程卻沒有。如果垃圾回收線程是非守護(hù)線程,當(dāng)JVM 要退出時(shí),由于垃圾回收線程還在運(yùn)行著,導(dǎo)致程序無法退出,這就很尷尬。這就是「為什么垃圾回收線程需要是守護(hù)線程啦」**。
「WeakHashMap」 類似HashMap ,不同點(diǎn)在WeakHashMap的key是**「弱引用」**的key。
談到**「弱引用」**,在這里回顧下四種引用吧
?強(qiáng)引用:Object obj=new Object()這種,只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。軟引用: 一般情況不會(huì)回收,如果內(nèi)存不夠要溢出時(shí)才會(huì)進(jìn)行回收弱引用:當(dāng)垃圾收集器開始工作,無論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象。虛引用:為一個(gè)對(duì)象設(shè)置虛引用的唯一目的只是為了能在這個(gè)對(duì)象被回收時(shí)收到一個(gè)系統(tǒng)的通知。?
正是因?yàn)閃eakHashMap使用的是弱引用,「它的對(duì)象可能隨時(shí)被回收」。WeakHashMap 類的行為部分**「取決于垃圾回收器的動(dòng)作」,調(diào)用兩次size()方法返回不同值,調(diào)用兩次isEmpty(),一次返回true,一次返回false都是「可能的」**。
WeakHashMap**「工作原理」**回答這兩點(diǎn):
?WeakHashMap具有弱引用的特點(diǎn):隨時(shí)被回收對(duì)象。 發(fā)生GC時(shí),WeakHashMap是如何將Entry移除的呢??
WeakHashMap內(nèi)部的Entry繼承了WeakReference,即弱引用,所以就具有了弱引用的特點(diǎn),「隨時(shí)可能被回收」??聪略创a哈:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { V value; final int hash; Entry<K,V> next; /** * Creates new entry. */ Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) { super(key, queue); this.value = value; this.hash = hash; this.next = next; } ......
「WeakHashMap是如何將Entry移除的?」 GC每次清理掉一個(gè)對(duì)象之后,引用對(duì)象會(huì)放到ReferenceQueue的,接著呢遍歷queue進(jìn)行刪除。WeakHashMap的增刪改查操作,就是直接/間接調(diào)用expungeStaleEntries()方法,達(dá)到及時(shí)清除過期entry的目的??梢钥聪耬xpungeStaleEntries源碼哈:
/** * Expunges stale entries from the table. */ private void expungeStaleEntries() { for (Object x; (x = queue.poll()) != null; ) { synchronized (queue) { @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) x; int i = indexFor(e.hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> p = prev; while (p != null) { Entry<K,V> next = p.next; if (p == e) { if (prev == e) table[i] = next; else prev.next = next; // Must not null out e.next; // stale entries may be in use by a HashIterator e.value = null; // Help GC size--; break; } prev = p; p = next; } } } }
語法糖(Syntactic Sugar),也稱糖衣語法,讓程序更加簡(jiǎn)潔,有更高的可讀性。Java 中最常用的語法糖主要有泛型、變長(zhǎng)參數(shù)、條件編譯、自動(dòng)拆裝箱、內(nèi)部類等12種。
語法糖一、switch 支持 String 與枚舉
語法糖二、 泛型
語法糖三、 自動(dòng)裝箱與拆箱
語法糖四 、 方法變長(zhǎng)參數(shù)
語法糖五 、 枚舉
語法糖六 、 內(nèi)部類
語法糖七 、條件編譯
語法糖八 、 斷言
語法糖九 、 數(shù)值字面量
語法糖十 、 for-each
語法糖十一 、 try-with-resource
語法糖十二、Lambda表達(dá)式
感興趣的朋友,可以看下這篇文章哈:不了解這12個(gè)語法糖,別說你會(huì)Java!
? 一般情況下,JVM的對(duì)象都放在堆內(nèi)存中(發(fā)生逃逸分析除外)。當(dāng)類加載檢查通過后,Java虛擬機(jī)開始為新生對(duì)象分配內(nèi)存。如果Java堆中內(nèi)存是絕對(duì)規(guī)整的,所有被使用過的的內(nèi)存都被放到一邊,空閑的內(nèi)存放到另外一邊,中間放著一個(gè)指針作為分界點(diǎn)的指示器,所分配內(nèi)存僅僅是把那個(gè)指針向空閑空間方向挪動(dòng)一段與對(duì)象大小相等的實(shí)例,這種分配方式就是“「指針碰撞」”。 ?
? 如果Java堆內(nèi)存中的內(nèi)存并不是規(guī)整的,已被使用的內(nèi)存和空閑的內(nèi)存相互交錯(cuò)在一起,不可以進(jìn)行指針碰撞啦,虛擬機(jī)必須維護(hù)一個(gè)列表,記錄哪些內(nèi)存是可用的,在分配的時(shí)候從列表找到一塊大的空間分配給對(duì)象實(shí)例,并更新列表上的記錄,這種分配方式就是“「空閑列表」” ?
對(duì)象創(chuàng)建在虛擬機(jī)中是非常頻繁的行為,可能存在線性安全問題。如果一個(gè)線程正在給A對(duì)象分配內(nèi)存,指針還沒有來的及修改,同時(shí)另一個(gè)為B對(duì)象分配內(nèi)存的線程,仍引用這之前的指針指向,這就出**「問題」**了。
? 可以把內(nèi)存分配的動(dòng)作按照線程劃分在不同的空間之中進(jìn)行,每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存,這就是**「TLAB(Thread Local Allocation Buffer,本地線程分配緩存)」** 。虛擬機(jī)通過-XX:UseTLAB設(shè)定它的。 ?
CMS(Concurrent Mark Sweep) 收集器:是一種以獲得最短回收停頓時(shí)間為目標(biāo)的收集器,標(biāo)記清除算法,運(yùn)作過程:「初始標(biāo)記,并發(fā)標(biāo)記,重新標(biāo)記,并發(fā)清除」,收集結(jié)束會(huì)產(chǎn)生大量空間碎片。如圖(下圖來源互聯(lián)網(wǎng)):
「CMS收集器和G1收集器的區(qū)別:」
CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
G1收集器收集范圍是老年代和新生代,不需要結(jié)合其他收集器使用;
CMS收集器以最小的停頓時(shí)間為目標(biāo)的收集器;
G1收集器可預(yù)測(cè)垃圾回收的停頓時(shí)間
CMS收集器是使用“標(biāo)記-清除”算法進(jìn)行的垃圾回收,容易產(chǎn)生內(nèi)存碎片
G1收集器使用的是“標(biāo)記-整理”算法,進(jìn)行了空間整合,降低了內(nèi)存空間碎片。
JVM調(diào)優(yōu)其實(shí)就是通過調(diào)節(jié)JVM參數(shù),即對(duì)垃圾收集器和內(nèi)存分配的調(diào)優(yōu),以達(dá)到更高的吞吐和性能。JVM調(diào)優(yōu)主要調(diào)節(jié)以下參數(shù)
「堆棧內(nèi)存相關(guān)」
?-Xms 設(shè)置初始堆的大小-Xmx 設(shè)置最大堆的大小-Xmn 設(shè)置年輕代大小,相當(dāng)于同時(shí)配置-XX:NewSize和-XX:MaxNewSize為一樣的值-Xss 每個(gè)線程的堆棧大小-XX:NewSize 設(shè)置年輕代大小(for 1.3/1.4)-XX:MaxNewSize 年輕代最大值(for 1.3/1.4)-XX:NewRatio 年輕代與年老代的比值(除去持久代)-XX:SurvivorRatio Eden區(qū)與Survivor區(qū)的的比值 -XX:PretenureSizeThreshold 當(dāng)創(chuàng)建的對(duì)象超過指定大小時(shí),直接把對(duì)象分配在老年代。-XX:MaxTenuringThreshold設(shè)定對(duì)象在Survivor復(fù)制的最大年齡閾值,超過閾值轉(zhuǎn)移到老年代?
「垃圾收集器相關(guān)」
?-XX:+UseParallelGC:選擇垃圾收集器為并行收集器。-XX:ParallelGCThreads=20:配置并行收集器的線程數(shù)-XX:+UseConcMarkSweepGC:設(shè)置年老代為并發(fā)收集。 -XX:CMSFullGCsBeforeCompaction=5 由于并發(fā)收集器不對(duì)內(nèi)存空間進(jìn)行壓縮、整理,所以運(yùn)行一段時(shí)間以后會(huì)產(chǎn)生“碎片”,使得運(yùn)行效率降低。此值設(shè)置運(yùn)行5次GC以后對(duì)內(nèi)存空間進(jìn)行壓縮、整理。-XX:+UseCMSCompactAtFullCollection:打開對(duì)年老代的壓縮??赡軙?huì)影響性能,但是可以消除碎片?
「輔助信息相關(guān)」
?-XX:+PrintGCDetails 打印GC詳細(xì)信息-XX:+ HeapDumpOnOutOfMemoryError讓JVM在發(fā)生內(nèi)存溢出的時(shí)候自動(dòng)生成內(nèi)存快照,排查問題用-XX:+DisableExplicitGC禁止系統(tǒng)System.gc(),防止手動(dòng)誤觸發(fā)FGC造成問題.-XX:+PrintTLAB 查看TLAB空間的使用情況?
關(guān)于JVM的面試真題有哪些就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。