JVM面試知識點梳理

yxc發表於2018-11-24

本文這個只是我自己整理的面試中多面到的一些JVM方面的知識點,就這樣看它們都是孤立的,我是看《深入Java虛擬機器》以及極客時間一個JVM系列的文章,才把這些概念給貫通的。

比如一個知識點:垃圾回收分Minor GC、Full GC。Minor發生在新生代,新生代分Eden、Survivor,而Survivor又分 From Survivor、To Survivor。新生代採用的是Copy機制的垃圾回收演算法,通過空間換時間,它是怎麼換的呢?To Survivor是一塊空的記憶體,垃圾回收時,會把Eden、From Survivor中的可達的(區別堆外不可達)物件搬到 To Survivor中去,然後清除Eden、Form Survivor,整個過程接受後,交換From跟To,這期間有可能把物件從Eden、From搬去To的時候,To記憶體不足則需要借用老生代的記憶體,這就是所謂的記憶體擔保。

Java記憶體結構

程式計數器:每個執行緒執行程式指令的行號

虛擬機器棧:存放每個方法的棧幀,幀的入棧跟出棧就是方法執行的過程

本地方法棧:Native方法的棧

Java堆:儲存Java物件的地方,細分為 Eden區, From Survivor空間, To Survivor空間(執行緒共享)

方法區:執行緒共享,存放已經被虛擬機器載入進來的類資訊,常量、靜態變數,JIT編譯後的資料程式碼。java的class檔案首先進入的到方法區裡面去。

執行時常量池

方法區的一部分。

四大引用

強引用:只要強引用還在,引用的物件永遠不會被回收

軟引用: 發生記憶體溢位之前會清除

弱引用:垃圾回收的時候會收走

虛引用:物件生命週期不會受到它影響,只是在被回收掉的時候,收到通知。

物件的存活
  • 引用計數器
  • 可達性演算法(堆外的引用, GC Roots)
垃圾回收演算法
  • 標記清除法
  • 複製演算法
  • 標記整理法
  • 分代收集
安全點、Minor GC、Full GC、 STW、TLAB

safePoint、安全區域 。(不操作記憶體的寫,不跟主存打交道) STW(stop the world)

eden, survior中呼叫 Minor GC,之所以沒有用STW,新增了統計老生代到 新生代資料引用的卡表(髒位)

新生代(eden + from + to)TLAB(Thread Local Allocation Buffers)由於to Survior的空間不夠,找老年代做記憶體擔保

當經歷過幾次 從from 到 to過程後依舊活著的物件就可以進入到 老生代中去了。

垃圾收集器
  • Serial (單執行緒,新生代,copy演算法)

  • Parallel New(多執行緒,新生代,copy演算法,時間優先)

  • Parallel Scavenge(多執行緒,新生代,copy演算法,吞吐量優先)

  • Serial Old(單執行緒,老生代, 標記清除)

  • Parallel Old(多執行緒,老生代, 標記清除)

  • CMS:垃圾收集器跟應用並行(多執行緒,老生代, 標記清除)分兩次清理,第一次清理的時候,工作執行緒跟垃圾執行緒並行,第二次STW。(Java 9 被廢除)

  • G1 (新生代 + 老生代)

類載入過程
  • 載入:驗證Class檔案是否按Class的結構走的,然後載入完成之後變成記憶體的結構
  • 驗證:不會等載入結束,其實交替進行的(檔案格式驗證、後設資料驗證、位元組碼驗證、符號引用驗證)
  • 準備:分配記憶體,虛方法動態分配的方便發表。建立符號引用(所以跟驗證是交叉的)
  • 解析:(符號引用到直接引用:欄位解析、類方法解析、介面方法解析)
  • 初始化 (cinit<>)
即時編譯(JIT優化)

熱點方法,迴圈熱點

C、C++屬於靜態編譯,執行時候不會。java屬於動態編譯,虛方法動態分配,激進優化等使之更徹底,但是佔用CPU。

優化方式:

  • 內聯優化(省去了方法的載入,壓棧等。詭異的TR1圖)

  • 逃逸分析(物件沒有被方法外引用的時候,直接到接近CPU的棧上分配儲存空間。方法僅被一個執行緒呼叫到的時候,去掉同步鎖。)

  • 指令集(Compare And Sweep, Compare and Set),???

  • 迴圈優化、向量優化

方法呼叫指令
  • invokespecial(例項構造方法、私有方法、父類方法)
  • invokestatic(靜態方法,靜態分配)
  • invokevirtual(呼叫所有虛方法)
  • invokeinterface(呼叫介面方法,執行時才)
  • invokedynamic (方法控制程式碼 MethodHandler, Lookup 不同於 反射,修改方法棧中的指令集)
反射的消耗
  • Method.invoke(Class, Object[])方法中只能夠是 Object型別,沒有基本資料型別。自動裝箱、拆箱損耗。
  • Method.invoke沒有辦法內聯了。
  • 沒有辦法逃逸分析。
  • 要進行方法找尋,還要往上查詢父類中的所有方法。儲存記錄
  • 本地實現、動態實現(超過一定的次數,動態實現會快一些)
Volatile
  • 修改記憶體可見性,沒有用執行緒中的暫存器作為快取
  • 禁止重排序,設定記憶體屏障(讀讀,讀寫,寫讀, 寫寫),新增指令 lock … 空指令,讓lock指令後的指令沒法重排到前面去(單執行緒裡面不管 資料相關性、happens—before)
執行緒、鎖
  • 偏向鎖 (當第一次進入到同步程式碼塊的時候,不上鎖,修改物件頭中的Mark word,執行緒的ID等資訊,通過CAS指令,全程只有一個執行緒,所以記錄它偏向它,偏向鎖)

  • 輕量級鎖 (只有一個執行緒,把執行緒上鎖記錄加入到 物件頭中去,採用CAS,不再是第一個執行緒,而是另外的執行緒不同時間段執行同步程式碼塊,有偏向鎖到 輕量級鎖)

  • 重量級鎖(一個執行緒在執行,另一個執行緒來競爭。輕量級鎖 鎖膨脹為重量級鎖)等待的執行緒沒有直接去wait,而是執行空指令處於就緒狀態,等待執行中的執行緒釋放鎖,然後自己拿取,這樣的叫自旋鎖。為什麼要新增這個自選狀態呢?

    當執行緒一旦進入到wait後,就統一交由CPU進行管理 + 排程,轉為核心態,到時候喚醒執行變為使用者態,這個過程的切換有很多資源的 消耗(記憶體的拷貝複製等),在這裡執行空指令消耗可能比這個切換更加省CPU。但是對於其他鎖而言就失去公平性了。

    這樣相比而言,協程有自己管理、排程部區分核心態、使用者態,資源開支比較小,是現代語言(Kotlin等)中常用的技術。(搶佔式排程、協同式)

synchronized(monitor, monitorenter, monitorexit)

ReentrantLock(重入鎖), NewCondtion

相關文章