首頁>技術>

原文連結:https://mp.weixin.qq.com/s/7ZaLMI1LYHBEL7Q2gOrGKQ

年底了,xjjdog決定來一篇實用的硬核文章。本篇文章多達38道面試題,照顧到了JVM的方方面面,都是常見的題目。如果背誦記憶下來,進入大廠非常的easy。

面試題不能坑人,所以本篇文章的內容是經過多次打磨的,現在放送給大家。

有些面試題是開放性的,有些是知識性的,注意區別。面試並沒有標準答案,尤其是開放性題目,你需要整理成白話文,來儘量的展示自己。

如果你在答案中描述了一些自己不是很熟悉的內容,可能會受到追問。所以,根據問題,整理一份適合自己的吧,這比拿來主義更讓人印象深刻。

1、JVM有哪些記憶體區域?(JVM的記憶體佈局是什麼?)

JVM包含堆、元空間、Java虛擬機器棧、本地方法棧、程式計數器等記憶體區域。其中,堆是佔用記憶體最大的一塊。我們平常的-Xmx、-Xms等引數,就是針對於堆進行設計的。

:JVM堆中的資料,是共享的,是佔用記憶體最大的一塊區域虛擬機器棧:Java虛擬機器棧,是基於執行緒的,用來服務位元組碼指令的執行程式計數器:當前執行緒所執行的位元組碼的行號指示器元空間:方法區就在這裡,非堆本地記憶體:其他的記憶體佔用空間2、Java的記憶體模型是什麼?(JMM是什麼?)

JVM試圖定義一種統一的記憶體模型,能將各種底層硬體及作業系統的記憶體訪問差異進行封裝,使Java程式在不同硬體及作業系統上都能達到相同的併發效果。它分為工作記憶體和主記憶體,執行緒無法對主儲存器直接進行操作,一個執行緒要和另外一個執行緒通訊,只能透過主存進行交換。

JMM可以說是Java併發的基礎,它的定義將直接影響多執行緒實現的機制,如果你想要想深入瞭解多執行緒併發中的相關問題現象,對JMM的深入研究是必不可少的。

上面兩個問題是經常容易搞混的,但它們的內容卻完全不同的。

3、JVM垃圾回收時候如何確定垃圾?什麼是GC Roots?

JVM採用的是可達性分析演算法。JVM是透過GC Roots來判定物件的存活的。從GC Roots向下追溯、搜尋,會產生一個叫做Reference Chain的鏈條。當一個物件不能和任何一個GC Root產生關係,就判定為垃圾。

GC Roots大體包括:

活動執行緒相關的各種引用,比如虛擬機器棧中棧幀裡的引用。類的靜態變數的引用。JNI引用等。

當然也有比較詳細的回答,個人認為這些就夠了。詳細版本如下:

Java執行緒中,當前所有正在被呼叫的方法的引用型別引數、區域性變數、臨時值等。也就是與我們棧幀相關的各種引用。所有當前被載入的Java類。Java類的引用型別靜態變數。執行時常量池裡的引用型別常量(String或Class型別)。JVM內部資料結構的一些引用,比如sun.jvm.hotspot.memory.Universe類。用於同步的監控物件,比如呼叫了物件的wait()方法。JNI handles,包括global handles和local handles4、能夠找到 Reference Chain 的物件,就一定會存活麼?

這不一定,還要看reference型別。弱引用會在GC時會被回收,軟引用會在記憶體不足的時候被回收。但沒有Reference Chain的物件就一定會被回收。

5、強引用、軟引用、弱引用、虛引用是什麼?

普通的物件引用關係就是強引用。

軟引用用於維護一些可有可無的物件。只有在記憶體不足時,系統則會回收軟引用物件,如果回收了軟引用物件之後仍然沒有足夠的記憶體,才會丟擲記憶體溢位異常。

弱引用物件相比較軟引用,要更加無用一些,它擁有更短的生命週期。當JVM進行垃圾回收時,無論記憶體是否充足,都會回收被弱引用關聯的物件。

虛引用是一種形同虛設的引用,在現實場景中用的不是很多,它主要用來跟蹤物件被垃圾回收的活動。

6、你說你做過JVM引數調優和引數配置,請問如何檢視JVM系統預設值

使用-XX:+PrintFlagsFinal引數可以看到引數的預設值。這個預設值還和垃圾回收器有關,比如UseAdaptiveSizePolicy。

7、你平時工作中用過的JVM常用基本配置引數有哪些?

Xmx、Xms、Xmn、MetaspaceSize等。

你只需要記憶10個左右即可應付絕大多數面試,建議只記憶G1相關引數。CMS這種既耗時間引數又多又被淘汰的東西,不看也罷。面試時間有限,不會在這上面糾結,除非你表現的太囂張了。

8、請你談談對OOM的認識

OOM是非常嚴重的問題,除了程式計數器,其他記憶體區域都有溢位的風險。和我們平常工作最密切的,就是堆溢位。另外,元空間在方法區內容非常多的情況下也會溢位。還有就是棧溢位,這個通常影響比較小。堆外也有溢位的可能,這個就比較難排查一些。

9、你都有哪些手段用來排查記憶體溢位?

(這個話題很大,可以從實踐環節中隨便摘一個進行總結,下面舉例一個最普通的)

你可以來一箇中規中矩的回答:

記憶體溢位包含很多種情況,我在平常工作中遇到最多的就是堆溢位。有一次線上遇到故障,重新啟動後,使用jstat命令,發現Old區在一直增長。我使用jmap命令,匯出了一份線上堆疊,然後使用MAT進行分析。透過對GC Roots的分析,我發現了一個非常大的HashMap物件,這個原本是有位同學做快取用的,但是一個無界快取,造成了堆記憶體佔用一直上升。後來,將這個快取改成 guava的Cache,並設定了弱引用,故障就消失了。

這個回答不是十分出彩,但著實是常見問題,讓人挑不出毛病。

10、GC垃圾回收演算法與垃圾收集器的關係?

常用的垃圾回收演算法有標記清除、標記整理、複製演算法等。引用計數器也算是一種,但是沒有垃圾回收器使用這種演算法,因為有迴圈依賴的問題。

很多垃圾回收器都是分代回收的。對於年輕代,主要有Serial、ParNew等垃圾回收器,回收過程主要使用複製演算法。

老年代的回收演算法有Serial、CMS等,主要使用標記清除、標記整理演算法等。

我們線上用的較多的是G1,也有年輕代和老年代的概念,不過它是一個整堆回收器,它的回收物件是小堆區 。

在目前G1大行其道的今天,實在沒必要再糾結CMS這麼難用的東西了。

11、生產上如何配置垃圾收集器的?

首先是記憶體大小問題,基本上每一個記憶體區域我都會設定一個上限,來避免溢位問題,比如元空間。通常,堆空間我會設定成作業系統的2/3(這是想給其他程序和作業系統預留一些時間),超過8GB的堆優先選用G1。

接下來,我會對JVM進行初步最佳化。比如根據老年代的物件提升速度,來調整年輕代和老年代之間的比例。

再接下來,就是專項最佳化,主要判斷的依據就是系統容量、訪問延遲、吞吐量等。我們的服務是高併發的,所以對STW的時間非常敏感。

我會透過記錄詳細的GC日誌,來找到這個瓶頸點,借用gceasy(重點)這樣的日誌分析工具,很容易定位到問題。之所以選擇採用工具,是因為gc日誌看起來實在是太麻煩了,gceasy號稱是AI學習分析問題,視覺化做的較好。

12、怎麼檢視伺服器預設的垃圾回收器是哪一個?

這通常會使用另外一個引數:-XX:+PrintCommandLineFlags可以列印所有的引數,包括使用的垃圾回收器。

13、假如生產環境CPU佔用過高,請談談你的分析思路和定位。

這個可真是太太太常見了,不過已經爛大街了。如果你還是一個有經驗的開發者,不知道的話,需要反省一下了。

首先,使用top -H命令獲取佔用CPU最高的執行緒,並將它轉化為16進位制。

然後,使用jstack命令獲取應用的棧資訊,搜尋這個16進位制。這樣能夠方便的找到引起CPU佔用過高的具體原因。

如果有條件的話,直接使用arthas就行操作就好了,不用再做這些費事費力的操作。

14、對於JDK自帶的監控和效能分析工具用過哪些?

jps:用來顯示Java程序;jstat:用來檢視GC;jmap:用來dump堆;jstack:用來dump棧;jhsdb:用來檢視執行中的記憶體資訊;

都是非常常用的工具,要熟練掌握。因為線上環境通常都有很多限制,用不了圖形化工具。當出現這些情況,上面的命令就是救命的。

15、棧幀都有哪些資料?

JVM的執行是基於棧的,和C語言的棧類似,它的大多數資料都是在堆裡面的,只有少部分執行時的資料存在於棧上。

在JVM中,每個執行緒棧裡面的元素,就叫棧幀。

棧幀包含:區域性變量表、運算元棧、動態連線、返回地址等。

16、JIT是什麼?

為了提高熱點程式碼的執行效率,在執行時,虛擬機器將會把這些程式碼編譯成與本地平臺相關的機器碼,並進行各種層次的最佳化。完成這個任務的編譯器,就稱為即時編譯器(Just In Time Compiler),簡稱 JIT 編譯器。

17、Java的雙親委託機制是什麼?

它的意思是,除了頂層的啟動類載入器以外,其餘的類載入器,在載入之前,都會委派給它的父載入器進行載入。這樣一層層向上傳遞,直到祖先們都無法勝任,它才會真正的載入。

Java預設是這種行為。當然Java中也有很多打破雙親行為的騷操作,比如SPI(JDBC驅動載入),OSGI等。

18、有哪些打破了雙親委託機制的案例?Tomcat可以載入自己目錄下的class檔案,並不會傳遞給父類的載入器。Java的SPI,發起者是BootstrapClassLoader,BootstrapClassLoader已經是最上層的了。它直接獲取了AppClassLoader進行驅動載入,和雙親委派是相反的。。19、簡單描述一下(分代)垃圾回收的過程

分代回收器有兩個分割槽:老生代和新生代,新生代預設的空間佔比總空間的 1/3,老生代的預設佔比是 2/3。

新生代使用的是複製演算法,新生代裡有 3 個分割槽:Eden、To Survivor、From Survivor,它們的預設佔比是 8:1:1,它的執行流程如下:

當年輕代中的Eden區分配滿的時候,就會觸發年輕代的GC(Minor GC)。具體過程如下:

在Eden區執行了第一次GC之後,存活的物件會被移動到其中一個Survivor分割槽(以下簡稱from)Eden區再次GC,這時會採用複製演算法,將Eden和from區一起清理。存活的物件會被複制到to區。接下來,只需要清空from區就可以了20、CMS分為哪幾個階段?

CMS已經棄用。生活美好,時間有限,不建議再深入研究了。如果碰到問題,直接祭出回收過程即可。

(1)初始標記 (2)併發標記 (3)併發預清理 (4)併發可取消的預清理 (5)重新標記 (6)併發清理

由於《深入理解java虛擬機器》一書的流行,面試時省略3、4步一般也是沒問題的。

21、CMS都有哪些問題?

(1)記憶體碎片問題。Full GC的整理階段,會造成較長時間的停頓。(2)需要預留空間,用來分配收集階段產生的“浮動垃圾“。(3)使用更多的CPU資源,在應用執行的同時進行堆掃描。(4)停頓時間是不可預期的。

正因為有這些問題,所以大家才用更加完備的G1。況且,現在都是大記憶體時代了,G1玩得轉,就沒必要用CMS。

22、你都用過G1垃圾回收器的哪幾個重要引數?

最重要的是MaxGCPauseMillis,可以透過它設定G1的目標停頓時間,它會盡量的去達成這個目標。G1HeapRegionSize可以設定小堆區的大小,一般是2的次冪。

InitiatingHeapOccupancyPercent,啟動併發GC時的堆記憶體佔用百分比。G1用它來觸發併發GC週期,基於整個堆的使用率,而不只是某一代記憶體的使用比例,預設是45%。

再多?不是專家,就沒必要要求別人也是。

23、GC日誌的real、user、sys是什麼意思?

real 實際花費的時間,指的是從開始到結束所花費的時間。比如程序在等待I/O完成,這個阻塞時間也會被計算在內。user 指的是程序在使用者態(User Mode)所花費的時間,只統計本程序所使用的時間,是指多核。sys 指的是程序在核心態(Kernel Mode)花費的CPU時間量,指的是核心中的系統呼叫所花費的時間,只統計本程序所使用的時間。

這個是用來看日誌用的,如果你不看日誌,那不瞭解也無妨。不過,這三個引數的意義,在你能看到的地方,基本上都是一致的,比如作業系統。

24、什麼情況會造成元空間溢位?

元空間(Metaspace)預設是沒有上限的,不加限制比較危險。當應用中的Java類過多,比如Spring等一些使用動態代理的框架生成了很多類,如果佔用空間超出了我們的設定值,就會發生元空間溢位。

所以,預設風險大,但如果你不給足它空間,它也會溢位。

25、什麼時候會造成堆外記憶體溢位?

使用了Unsafe類申請記憶體,或者使用了JNI對記憶體進行操作。這部分記憶體是不受JVM控制的,不加限制的使用,容易發生記憶體溢位。

26、SWAP會影響效能麼?

當作業系統記憶體不足的時候,會將部分資料寫入到SWAP交換分中,但是SWAP的效能是比較低的。如果應用的訪問量較大,需要頻繁申請和銷燬記憶體,就容易發生卡頓。一般高併發場景下,會禁用SWAP。

27、有什麼堆外記憶體的排查思路?

程序佔用的記憶體,可以使用top命令,看RES段佔用的值。如果這個值大大超出我們設定的最大堆記憶體,則證明堆外記憶體佔用了很大的區域。

使用gdb可以將物理記憶體dump下來,通常能看到裡面的內容。更加複雜的分析可以使用perf工具,或者谷歌開源的gperftools。那些申請記憶體最多的native函式,很容易就可以找到。

28、HashMap中的key,可以是普通物件麼?需要什麼注意的地方?

Map的key和value都可以是任何型別。但要注意的是,一定要重寫它的equals和hashCode方法,否則容易發生記憶體洩漏。

29、怎麼看死鎖的執行緒?

透過jstack命令,可以獲得執行緒的棧資訊。死鎖資訊會在非常明顯的位置(一般是最後)進行提示。

30、如何寫一段簡單的死鎖程式碼?

這個筆試的話頻率也挺高(遇見筆試的公司要三思啊),所以這裡直接給出一個答案(有很多版本的)。

public class DeadLockDemo {    public static void main(String[] args) {        Object object1 = new Object();        Object object2 = new Object();        Thread t1 = new Thread(() -> {            synchronized (object1) {                try {                    Thread.sleep(200);                } catch (InterruptedException e) {                    e.printStackTrace();                }                synchronized (object2) {                }            }        }, "deadlock-demo-1");        t1.start();        Thread t2 = new Thread(() -> {            synchronized (object2) {                synchronized (object1) {                }            }        }, "deadlock-demo-2");        t2.start();    }}
31、invokedynamic指令是幹什麼的?

屬於比較高階的題目。沒看過虛擬機器的一般是不知道的。所以如果你不太熟悉,不要氣餒,加油!(小拳拳錘你胸口)。

invokedynamic是Java7之後新加入的位元組碼指令,使用它可以實現一些動態型別語言的功能。我們使用的Lambda表示式,在位元組碼上就是invokedynamic指令實現的。它的功能有點類似反射,但它是使用方法控制代碼實現的,執行效率更高。

32、volatile關鍵字的原理是什麼?幹什麼用的?

使用了volatile關鍵字的變數,每當變數的值有變動的時候,都會將更改立即同步到主記憶體中;而如果某個執行緒想要使用這個變數,就先要從主存中重新整理到工作記憶體,這樣就確保了變數的可見性。

一般使用一個volatile修飾的bool變數,來控制執行緒的執行狀態。

volatile boolean stop = false;  void stop(){  this.stop = true; } void start(){  new Thread(()->{   while (!stop){    //sth   }  }).start(); }
33、什麼是方法內聯?

為了減少方法呼叫的開銷,可以把一些短小的方法,比如getter/setter,納入到目標方法的呼叫範圍之內,就少了一次方法呼叫,速度就能得到提升,這就是方法內聯的概念。

34、物件是怎麼從年輕代進入老年代的?

這是老掉牙的題目了。在下面四種情況下,物件會從年輕代進入老年代。

如果物件夠老,會透過提升(Promotion)進入老年代,這一般是根據物件的年齡進行判斷的。動態物件年齡判定。有的垃圾回收演算法,比如G1,並不要求age必須達到15才能晉升到老年代,它會使用一些動態的計算方法。分配擔保。當 Survivor 空間不夠的時候,就需要依賴其他記憶體(指老年代)進行分配擔保。這個時候,物件也會直接在老年代上分配。超出某個大小的物件將直接在老年代分配。不過這個值預設為0,意思是全部首選Eden區進行分配。35、safepoint是什麼?

STW並不會只發生在記憶體回收的時候。現在程式設計師這麼卷,碰到幾次safepoint的問題機率也是比較大的。

當發生GC時,使用者執行緒必須全部停下來,才可以進行垃圾回收,這個狀態我們可以認為JVM是安全的(safe),整個堆的狀態是穩定的。

如果在GC前,有執行緒遲遲進入不了safepoint,那麼整個JVM都在等待這個阻塞的執行緒,造成了整體GC的時間變長。

36、MinorGC,MajorGC、FullGC都什麼時候發生?

MinorGC在年輕代空間不足的時候發生,MajorGC指的是老年代的GC,出現MajorGC一般經常伴有MinorGC。

FullGC有三種情況。

當老年代無法再分配記憶體的時候元空間不足的時候顯示呼叫System.gc的時候。另外,像CMS一類的垃圾回收器,在MinorGC出現promotion failure的時候也會發生FullGC37、類載入有幾個過程?

載入、驗證、準備、解析、初始化。

38、什麼情況下會發生棧溢位?

棧的大小可以透過-Xss引數進行設定,當遞迴層次太深的時候,就會發生棧溢位。比如迴圈呼叫,遞迴等。

12
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Retrofit2.0入門教程