JVM 面試題【中級】

這個章節記錄一些 JVM 面試中,比較深刻的點java


static 靜態代碼塊和 static 變量誰先賦值

這算是一個和很差回答的問題了,乾巴巴說你不必定明白我說的是啥,仍是先看看代碼面試

public class Max {

    static {
        staticIntValue = 300;
    }
    
    public static int staticIntValue = 100;

    public final int finalIntValue = 3;

    public int intValue = 1;

    public static void main(String[] args) {
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
複製代碼
  • 【問:】
    問題就是 staticIntValue 到底等於多少,
  • 【答案:】
    staticIntValue=100

我先把答案說出來,是爲了你們能想一想,這個問題在在好幾個培訓機構的公開課上都看到過,可是很難有講到位的。這個世界上任務事情都有其根本的道理,有的看着天馬行空,其實根本是咱們沒有這快的知識體系,因此無從下手緩存

那麼這個問題如何分析呢?上面咱們寫的是 java 代碼,java 代碼會編譯成字節碼,怎麼賦值,賦值的前後順序其實都是在編譯時就決定好了的,咱們反編譯下字節碼就清除了安全

涉及的知識點是:static 的屬性申請內存空間是在類加載的驗證機階段,這個階段會給 static 屬性一個默認值,而後把 static 屬性的賦值和 static 代碼塊結合生成一個類的初始化方法 。 中的賦值順序和代碼書寫順序同樣,誰寫在最後,static 的值就是哪一個優化

先看上文代碼,static 代碼快在前spa

public class Max {

    static {
        staticIntValue = 300;
    }
    
    public static int staticIntValue = 100;
}
複製代碼

反編譯的字節碼線程

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: sipush        300
         3: putstatic     #9                  // Field staticIntValue:I
         6: bipush        100
         8: putstatic     #9                  // Field staticIntValue:I
        11: return
      LineNumberTable:
        line 11: 0
        line 14: 6
}
複製代碼

類加載準備階段先staticIntValue開闢內存地址,賦默認值0,而後先賦值300,在賦值100設計

先換個順序看看,static 代碼塊在後面code

public class Max {

    public static int staticIntValue = 100;
    
    static {
        staticIntValue = 300;
    }
}
複製代碼

反編譯的字節碼cdn

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        100
         2: putstatic     #9                  // Field staticIntValue:I
         5: sipush        300
         8: putstatic     #9                  // Field staticIntValue:I
        11: return
      LineNumberTable:
        line 10: 0
        line 13: 5
        line 14: 11
}
複製代碼

類加載準備階段先staticIntValue開闢內存地址,賦默認值0,而後先賦值100,在賦值300

其實很簡單,只要咱們找對了問題對應的知識體系,一切問題其實都沒有那麼難


static 儲存在哪

這個問題其實有陷阱啊,咱們通常都會這麼寫:String name = new String("AA");,這道題能夠問,static 變量=號左邊和右邊分別儲存在哪,我想不少人其實說不清楚

看代碼:

public class Max {

    public static byte[] values = new byte[1024*1024*60];

    public static void main(String[] args) {
        System.out.println("values: "+values);
    }
    
}

複製代碼

咱們先看=號右邊new 的部分: 方法很簡單,只要把堆棧信息打印出來就好了,代碼裏咱們搞了一個 60M 的變量出來

// 堆內存
Heap

 // 年輕代
 PSYoungGen      total 38400K, used 4663K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 14% used [0x0000000795580000,0x0000000795a0dc88,0x0000000797600000)
  from space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
  to   space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
  
 // 老年代 
 ParOldGen       total 87552K, used 61440K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 70% used [0x0000000740000000,0x0000000743c00010,0x0000000745580000)
  
 // 元空間 
 Metaspace       used 3387K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 388K, committed 512K, reserved 1048576K
複製代碼

很明顯,這60M內存開闢在了老年代裏面,對於單個對象體積大於年輕代的通常直接放到老年代裏,可是這裏60M顯然還夠檔次,我這能夠是 Mac Pro 跑的代碼


雙親委派機制

這裏寫雙親委派機制是爲了再一次強調該點,面試問的太多啦

咱們先回國頭來再看一遍 ClassLoader 的設計:

public abstract class ClassLoader{
    
    private final ClassLoader parent;
    
    protected Class<?> loadClass(String name, boolean resolve){
        ......
    }
}
複製代碼

ClassLoader 對象設計有 parent 父加載器,你們看着像不像鏈表。鏈表的next指向下一個,ClassLoader parent 這裏上一層級

類加載加載機制中默認不會直接由本身加載,會先用本身的父加載器 parent 去加載,父加載器加載不到再本身加載

JVM 3級類加載器,每一級都有本身能加載類的範圍,類加載器一級一級提交給父加載器去加載,每一級類加載在碰到本身能加載的類時,沒加載過的會去加載,加載過的會返回已經加載的class對象給下一級

看看 ClassLoader.loadClass() 方法代碼:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }
複製代碼

這個就叫作:雙親委派機制,爲啥叫雙親,由於系統類加載器上面就2級類加載器

java 核心類庫有訪問權限限制,類加載器在發現容許加載範圍以外的類加載的加載請求以後,會直接報錯的。這個判斷通常都是用包名來判斷的,好比你本身搞了一個 String 類,包名仍是 java.lang,那引導類加載器在處理這個加載請求時會直接報錯,這種報錯機制就叫作沙箱安全機制

沙箱安全機制有個典型例子:360沙箱隔離,好比U盤程序只在360認爲隔離出來的沙箱內運行,以保護沙箱外的系統不受可能的病毒污染

雙親委派機制的目的是爲了保證安全,防止核心 API 被篡改


棧內存在內存的哪塊

我想這絕對是能夠把你問懵逼的一道問題 o( ̄▽ ̄)d

其實這涉及到 JVM 一個機制:棧頂緩存 技術

因爲操做數是存儲在內存中的,所以會頻繁的執行內存讀/寫操做,必然會影響執行速度。緯二路解決這個問題,Hotspot 虛擬機的設計者們提出了棧頂緩存技術,講棧頂元素也就是立刻要執行的棧幀(方法)緩存在cpu寄存器中,以此下降對內存的讀寫次數,提高執行引擎的效率

啦啦,就是這麼簡單,棧內存和堆內存同樣不用的時候仍是保存在主內存也就是屋裏內存中,只有在線程搶到cpu'時間片的時候,纔會把棧內存棧頂的棧幀加載進cpu緩存中,至因而:L1->L2->L3 就不得而知了,估計應該是L1的面更大。完事根據java鎖升級機制還會對這塊有優化,好比很快會再次運行的線程的棧頂棧幀可能會提早加載進cpu緩存,此次估計就是L3了

相關文章
相關標籤/搜索