天天看點

JVM面試:當面試官問到你這十個問題,你答得上來嗎?

作者:Java靈風

前言

本文一起整理了10個經典又容易被疏忽的JVM面試題,希望能幫的上大家。

github位址,感謝每顆star

​https://github.com/whx123/JavaHome​​

1. 對象一定配置設定在堆中嗎?有沒有了解逃逸分析技術?

「對象一定配置設定在堆中嗎?」 不一定的,JVM通過「逃逸分析」,那些逃不出方法的對象會在棧上配置設定。

  • 「什麼是逃逸分析?」

逃逸分析(Escape Analysis),是一種可以有效減少Java 程式中同步負載和記憶體堆配置設定壓力的跨函數全局資料流分析算法。通過逃逸分析,Java Hotspot編譯器能夠分析出一個新的對象的引用的使用範圍,進而決定是否要将這個對象配置設定到堆上。

逃逸分析是指分析指針動态範圍的方法,它同編譯器優化原理的指針分析和外形分析相關聯。當變量(或者對象)在方法中配置設定後,其指針有可能被傳回或者被全局引用,這樣就會被其他方法或者線程所引用,這種現象稱作指針(或者引用)的逃逸(Escape)。通俗點講,如果一個對象的指針被多個方法或者線程引用時,那麼我們就稱這個對象的指針發生了逃逸。

  • 「一個逃逸分析的例子」
/**
 *  @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,不發生逃逸
    public String notEscape(String a, String b) {
        //
        StringBuilder str = new StringBuilder();
        str.append(a);
        str.append(b);
        return str.toString();
    }

    //外部線程可見object,發生逃逸
    public void objectEscape(){
        object = new Object();
    }

    //僅方法内部可見,不發生逃逸
    public void objectNotEscape(){
        Object object = new Object();
    }
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.           

「逃逸分析的好處」

棧上配置設定,可以降低垃圾收集器運作的頻率。同步消除,如果發現某個對象隻能從一個線程可通路,那麼在這個對象上的操作可以不需要同步。标量替換,把對象分解成一個個基本類型,并且記憶體配置設定不再是配置設定在堆上,而是配置設定在棧上。這樣的好處有,一、減少記憶體使用,因為不用生成對象頭。二、程式記憶體回收效率高,并且GC頻率也會減少。

2.虛拟機為什麼使用元空間替換了永久代?

「什麼是元空間?什麼是永久代?為什麼用元空間代替永久代?」 我們先回顧一下「方法區」吧,看看虛拟機運作時資料記憶體圖,如下:

JVM面試:當面試官問到你這十個問題,你答得上來嗎?

方法區和堆一樣,是各個線程共享的記憶體區域,它用于存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯後的代碼等資料。

「什麼是永久代?它和方法區有什麼關系呢?」

如果在HotSpot虛拟機上開發、部署,很多程式員都把方法區稱作永久代。可以說方法區是規範,永久代是Hotspot針對該規範進行的實作。在Java7及以前的版本,方法區都是永久代實作的。

「什麼是元空間?它和方法區有什麼關系呢?」

對于Java8,HotSpots取消了永久代,取而代之的是元空間(Metaspace)。換句話說,就是方法區還是在的,隻是實作變了,從永久代變為元空間了。

「為什麼使用元空間替換了永久代?」

  • 永久代的方法區,和堆使用的實體記憶體是連續的。
JVM面試:當面試官問到你這十個問題,你答得上來嗎?

「永久代」是通過以下這兩個參數配置大小的~

  • -XX:PremSize:設定永久代的初始大小
  • -XX:MaxPermSize: 設定永久代的最大值,預設是64M

對于「永久代」,如果動态生成很多class的話,就很可能出現「java.lang.OutOfMemoryError: PermGen space錯誤」,因為永久代空間配置有限嘛。最典型的場景是,在web開發比較多jsp頁面的時候。

  • JDK8之後,方法區存在于元空間(Metaspace)。實體記憶體不再與堆連續,而是直接存在于本地記憶體中,理論上機器「記憶體有多大,元空間就有多大」。
JVM面試:當面試官問到你這十個問題,你答得上來嗎?

可以通過以下的參數來設定元空間的大小:

-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就适當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,适當提高該值。-XX:MaxMetaspaceSize,最大空間,預設是沒有限制的。-XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少為配置設定空間所導緻的垃圾收集-XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少為釋放空間所導緻的垃圾收集

「是以,為什麼使用元空間替換永久代?」

表面上看是為了避免OOM異常。因為通常使用PermSize和MaxPermSize設定永久代的大小就決定了永久代的上限,但是不是總能知道應該設定為多大合适, 如果使用預設值很容易遇到OOM錯誤。當使用元空間時,可以加載多少類的中繼資料就不再由MaxPermSize控制, 而由系統的實際可用空間來控制啦。

3.什麼是Stop The World ? 什麼是OopMap?什麼是安全點?

進行垃圾回收的過程中,會涉及對象的移動。為了保證對象引用更新的正确性,必須暫停所有的使用者線程,像這樣的停頓,虛拟機設計者形象描述為「Stop The World」。

在HotSpot中,有個資料結構(映射表)稱為「OopMap」。一旦類加載動作完成的時候,HotSpot就會把對象内什麼偏移量上是什麼類型的資料計算出來,記錄到OopMap。在即時編譯過程中,也會在「特定的位置」生成 OopMap,記錄下棧上和寄存器裡哪些位置是引用。

這些特定的位置主要在:

  • 1.循環的末尾(非 counted 循環)
  • 2.方法臨傳回前 / 調用方法的call指令後
  • 3.可能抛異常的位置

這些位置就叫作「安全點(safepoint)。」 使用者程式執行時并非在代碼指令流的任意位置都能夠在停頓下來開始垃圾收集,而是必須是執行到安全點才能夠暫停。

4.說一下JVM 的主要組成部分及其作用?

JVM面試:當面試官問到你這十個問題,你答得上來嗎?

JVM包含兩個子系統和兩個元件,分别為

Class loader(類裝載子系統)Execution engine(執行引擎子系統);Runtime data area(運作時資料區元件)Native Interface(本地接口元件)。

  • 「Class loader(類裝載):」根據給定的全限定名類名(如:java.lang.Object)來裝載class檔案到運作時資料區的方法區中。
  • 「Execution engine(執行引擎)」:執行class的指令。
  • 「Native Interface(本地接口):」與native lib互動,是其它程式設計語言互動的接口。
  • 「Runtime data area(運作時資料區域)」:即我們常說的JVM的記憶體。

首先通過編譯器把 Java源代碼轉換成位元組碼,Class loader(類裝載)再把位元組碼加載到記憶體中,将其放在運作時資料區的方法區内,而位元組碼檔案隻是 JVM 的一套指令集規範,并不能直接交給底層作業系統去執行,是以需要特定的指令解析器執行引擎(Execution Engine),将位元組碼翻譯成底層系統指令,再交由 CPU 去執行,而這個過程中需要調用其他語言的本地庫接口(Native Interface)來實作整個程式的功能。

5. 守護線程是什麼?守護線程和非守護線程的差別是?守護線程的作用是?

「守護線程」是差別于使用者線程哈,「使用者線程」即我們手動建立的線程,而守護線程是程式運作的時候在背景提供一種「通用服務的線程」。垃圾回收線程就是典型的守護線程。

「守護線程和非守護線程的差別是?」 我們通過例子來看吧~

/**
      * 
      */
    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) {
                    }
                }
        });
        //标記為守護線程
        t1.setDaemon(true);
        //啟動線程
        t1.start();

        Thread.sleep(3000);
        System.out.println("主線程執行完畢...");
    }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.           

運作結果:

JVM面試:當面試官問到你這十個問題,你答得上來嗎?

可以發現标記為守護線程後,「主線程銷毀停止,守護線程一起銷毀」。我們再看下,去掉 t1.setDaemon(true)守護标記的效果:

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) {
                    }
                }
        });
        //啟動線程
        t1.start();

        Thread.sleep(3000);
        System.out.println("主線程執行完畢...");
    }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.           
JVM面試:當面試官問到你這十個問題,你答得上來嗎?

是以,當主線程退出時,JVM 也跟着退出運作,守護線程同時也會被回收,即使是死循環。如果是使用者線程,它會一直停在死循環跑。這就是「守護線程和非守護線程的差別」啦。

守護線程擁有「自動結束自己生命周期的特性」,非守護線程卻沒有。如果垃圾回收線程是非守護線程,當JVM 要退出時,由于垃圾回收線程還在運作着,導緻程式無法退出,這就很尴尬。這就是「為什麼垃圾回收線程需要是守護線程啦」。

6.WeakHashMap了解過嘛?它是怎麼工作的?

「WeakHashMap」 類似HashMap ,不同點在WeakHashMap的key是「弱引用」的key。

談到「弱引用」,在這裡回顧下四種引用吧

強引用:Object obj=new Object()這種,隻要強引用關系還存在,垃圾收集器就永遠不會回收掉被引用的對象。軟引用: 一般情況不會回收,如果記憶體不夠要溢出時才會進行回收弱引用:當垃圾收集器開始工作,無論目前記憶體是否足夠,都會回收掉隻被弱引用關聯的對象。虛引用:為一個對象設定虛引用的唯一目的隻是為了能在這個對象被回收時收到一個系統的通知。

正是因為WeakHashMap使用的是弱引用,「它的對象可能随時被回收」。WeakHashMap 類的行為部分「取決于垃圾回收器的動作」,調用兩次size()方法傳回不同值,調用兩次isEmpty(),一次傳回true,一次傳回false都是「可能的」。

WeakHashMap「工作原理」回答這兩點:

❝​

WeakHashMap具有弱引用的特點:随時被回收對象。發生GC時,WeakHashMap是如何将Entry移除的呢?

WeakHashMap内部的Entry繼承了WeakReference,即弱引用,是以就具有了弱引用的特點,「随時可能被回收」。看下源碼哈:

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;
        }
        ......1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.           

「WeakHashMap是如何将Entry移除的?」

/**
     * 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;
                }
            }
        }
    }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.           

7. 是否了解Java文法糖嘛?說下12種Java中常用的文法糖?

文法糖(Syntactic Sugar),也稱糖衣文法,讓程式更加簡潔,有更高的可讀性。Java 中最常用的文法糖主要有泛型、變長參數、條件編譯、自動拆裝箱、内部類等12種。

  • 文法糖一、switch 支援 String 與枚舉
  • 文法糖二、 泛型
  • 文法糖三、 自動裝箱與拆箱
  • 文法糖四 、 方法變長參數
  • 文法糖五 、 枚舉
  • 文法糖六 、 内部類
  • 文法糖七 、條件編譯
  • 文法糖八 、 斷言
  • 文法糖九 、 數值字面量
  • 文法糖十 、 for-each
  • 文法糖十一 、 try-with-resource
  • 文法糖十二、Lambda表達式

感興趣的朋友,可以看下這篇文章哈:​​不了解這12個文法糖,别說你會Java!​​

8. 什麼是指針碰撞?什麼是空閑清單?什麼是TLAB?

一般情況下,JVM的對象都放在堆記憶體中(發生逃逸分析除外)。當類加載檢查通過後,Java虛拟機開始為新生對象配置設定記憶體。如果Java堆中記憶體是絕對規整的,所有被使用過的的記憶體都被放到一邊,空閑的記憶體放到另外一邊,中間放着一個指針作為分界點的訓示器,所配置設定記憶體僅僅是把那個指針向空閑空間方向挪動一段與對象大小相等的執行個體,這種配置設定方式就是“「指針碰撞」”。

JVM面試:當面試官問到你這十個問題,你答得上來嗎?

如果Java堆記憶體中的記憶體并不是規整的,已被使用的記憶體和空閑的記憶體互相交錯在一起,不可以進行指針碰撞啦,虛拟機必須維護一個清單,記錄哪些記憶體是可用的,在配置設定的時候從清單找到一塊大的空間配置設定給對象執行個體,并更新清單上的記錄,這種配置設定方式就是“「空閑清單」”

對象建立在虛拟機中是非常頻繁的行為,可能存線上性安全問題。如果一個線程正在給A對象配置設定記憶體,指針還沒有來的及修改,同時另一個為B對象配置設定記憶體的線程,仍引用這之前的指針指向,這就出「問題」了。

可以把記憶體配置設定的動作按照線程劃分在不同的空間之中進行,每個線程在Java堆中預先配置設定一小塊記憶體,這就是「TLAB(Thread Local Allocation Buffer,本地線程配置設定緩存)」 。虛拟機通過-XX:UseTLAB設定它的。

9.CMS垃圾回收器的工作過程,CMS收集器和G1收集器的差別。

CMS(Concurrent Mark Sweep) 收集器:是一種以獲得最短回收停頓時間為目标的收集器,标記清除算法,運作過程:「初始标記,并發标記,重新标記,并發清除」,收集結束會産生大量空間碎片。如圖(下圖來源網際網路):

JVM面試:當面試官問到你這十個問題,你答得上來嗎?

「CMS收集器和G1收集器的差別:」

  • CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
  • G1收集器收集範圍是老年代和新生代,不需要結合其他收集器使用;
  • CMS收集器以最小的停頓時間為目标的收集器;
  • G1收集器可預測垃圾回收的停頓時間
  • CMS收集器是使用“标記-清除”算法進行的垃圾回收,容易産生記憶體碎片
  • G1收集器使用的是“标記-整理”算法,進行了空間整合,降低了記憶體空間碎片。

10.JVM 調優

JVM調優其實就是通過調節JVM參數,即對垃圾收集器和記憶體配置設定的調優,以達到更高的吞吐和性能。JVM調優主要調節以下參數

JVM面試:當面試官問到你這十個問題,你答得上來嗎?

「堆棧記憶體相關」

-Xms 設定初始堆的大小-Xmx 設定最大堆的大小-Xmn 設定年輕代大小,相當于同時配置-XX:NewSize和-XX:MaxNewSize為一樣的值-Xss 每個線程的堆棧大小-XX:NewSize 設定年輕代大小(for 1.3/1.4)-XX:MaxNewSize 年輕代最大值(for 1.3/1.4)-XX:NewRatio 年輕代與年老代的比值(除去持久代)-XX:SurvivorRatio Eden區與Survivor區的的比值-XX:PretenureSizeThreshold 當建立的對象超過指定大小時,直接把對象配置設定在老年代。-XX:MaxTenuringThreshold設定對象在Survivor複制的最大年齡門檻值,超過門檻值轉移到老年代

「垃圾收集器相關」

-XX:+UseParallelGC:選擇垃圾收集器為并行收集器。-XX:ParallelGCThreads=20:配置并行收集器的線程數-XX:+UseConcMarkSweepGC:設定年老代為并發收集。-XX:CMSFullGCsBeforeCompaction=5 由于并發收集器不對記憶體空間進行壓縮、整理,是以運作一段時間以後會産生“碎片”,使得運作效率降低。此值設定運作5次GC以後對記憶體空間進行壓縮、整理。-XX:+UseCMSCompactAtFullCollection:打開對年老代的壓縮。可能會影響性能,但是可以消除碎片

「輔助資訊相關」

-XX:+PrintGCDetails 列印GC詳細資訊-XX:+HeapDumpOnOutOfMemoryError讓JVM在發生記憶體溢出的時候自動生成記憶體快照,排查問題用-XX:+DisableExplicitGC禁止系統System.gc(),防止手動誤觸發FGC造成問題.-XX:+PrintTLAB 檢視TLAB空間的使用情況

原文:https://blog.csdn.net/weiwenhou/article/details/110017975