JVM原理速記複習Java虛擬機總結思惟導圖面試必備

良心製做,右鍵另存爲保存

JVM
喜歡能夠點個贊哦

Java虛擬機

1、運行時數據區域

線程私有

  • 程序計數器java

    • 記錄正在執行的虛擬機字節碼指令的地址(若是正在執行的是Native方法則爲空),是惟一一個沒有規定OOM(OutOfMemoryError)的區域。
  • Java虛擬機棧程序員

    • 每一個Java方法在執行的同時會建立一個棧楨用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。從方法調用直到執行完成的過程,對應着一個棧楨在Java虛擬機棧中入棧和出棧的過程。(局部變量包含基本數據類型、對象引用reference和returnAddress類型)
  • 本地方法棧算法

    • 本地方法棧與Java虛擬機棧相似,它們之間的區別只不過是本地方法棧爲Native方法服務。

線程公有

  • Java堆(GC區)(Java Head)數組

    • 幾乎全部的對象實例都在這裏分配內存,是垃圾收集器管理的主要區域。分爲新生代和老年代。對於新生代又分爲Eden空間、From Survivor空間、To Survivor空間。
  • JDK1.7 方法區(永久代)安全

    • 用於存放已被加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。 對這塊區域進行垃圾回收的主要目的是對常量池的回收和對類的卸載,可是通常難以實現。 HotSpot虛擬機把它當作永久代來進行垃圾回收。但很難肯定永久代的大小,由於它受到不少因素的影響,而且每次Full GC以後永久代的大小都會改變,因此常常拋出OOM異常。 從JDK1.8開始,移除永久代,並把方法區移至元空間。數據結構

    • 運行時常量池多線程

      • 是方法區的一部分 Class文件中的常量池(編譯器生成的字面量和符號引用)會在類加載後被放入這個區域。 容許動態生成,例如String類的intern()
  • JDK1.8 元空間併發

    • 本來存在方法區(永久代)的數據,一部分移到了Java堆裏面,一部分移到了本地內存裏面(即元空間)。元空間存儲類的元信息,靜態變量和常量池等放入堆中。
  • 直接內存函數

    • 在NIO中,會使用Native函數庫直接分配堆外內存。

2、HotSpot虛擬機

對象的建立

  • 當虛擬機遇到一條new指令時
  1. 檢查參數可否在常量池中找到符號引用,並檢查這個符號引用表明的類是否已經被加載、解析和初始過,沒有的話先執行相應的類加載過程。
  2. 在類加載檢查經過以後,接下來虛擬機將爲新生對象分配內存。
  3. 內存分配完成以後,虛擬機須要將分配到的內存空間都初始化爲零值(不包括對象頭)。
  4. 對對象頭進行必要的設置。
  5. 執行構造方法按照程序員的意願進行初始化。

對象的內存佈局

    1. 對象頭
      1. 第一部分用於存儲對象自身的運行時數據,如哈希碼、GC分代年齡、鎖狀態標識、線程持有的鎖、偏向線程ID、偏向實現戳等。
      1. 第二部分是類型指針,即對象指向它的類元數據的指針(若是使用直接對象指針訪問),虛擬機經過這個指針來肯定這個對象是哪一個類的實例。
      1. 若是對象是一個Java數組的話,還須要第三部分記錄數據長度的數據。
    1. 實例數據
    • 是對象真正存儲的有效信息,也就是在代碼中定義的各類類型的字段內容。
    1. 對齊填充
    • 不是必然存在的,僅僅起着佔位符的做用。 HotSpot須要對象的大小必須是8字節的整數倍。

對象的訪問定位

  • 句柄訪問佈局

    • 在Java堆中劃分出一塊內存做爲句柄池。 Java棧上的對象引用reference中存儲的就是對象的句柄地址,而句柄中包含了到對象實例數據的指針和到對象類型數據的指針。 對象實例數據在Java堆中,對象類型數據在方法區(永久代)中。 優勢:在對象被移動時只會改變句柄中的實例數據指針,而對象引用自己不須要修改。
  • 直接指針訪問(HotSpot使用)

    • Java棧上的對象引用reference中存儲的就是對象的直接地址。 在堆中的對象實例數據就須要包含到對象類型數據的指針。 優勢:節省了一次指針定位的時間開銷,速度更快。

3、垃圾收集

概述

  • 垃圾收集主要是針對Java堆和方法區。 程序計數器、Java虛擬機棧個本地方法棧三個區域屬於線程私有,線程或方法結束以後就會消失,所以不須要對這三個區域進行垃圾回收。

判斷對象是否能夠被回收

  • 第一次標記(緩刑)

    • 引用計數算法

      • 給對象添加一個引用計數器,當對象增長一個引用時引用計數值++,引用失效時引用計數值--,引用計數值爲0時對象能夠被回收。

可是它難以解決對象之間的相互循環引用的狀況,此時這個兩個對象引用計數值爲1,可是永遠沒法用到這兩個對象。

- 可達性分析算法(Java使用)

	- 以一系列GC Roots的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連是,則證實此對象不可用,能夠被回收。
複製代碼

GC Roots對象包括

  1. 虛擬機棧(棧楨中的本地變量表)中引用的對象。
  2. 方法區中共類靜態屬性引用的對象。
  3. 方法區中常量引用的對象。
  4. 本地方法棧中JNI(即通常說的Native方法)引用的對象。
  • 第二次標記

    • 當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過。 若是對象在finalize方法中從新與引用鏈上的任何一個對象創建關聯則將不會被回收。

    • finalize()

      • 任何一個對象的finalize()方法都只會被系統調用一次。 它的出現是一個妥協,運行代價高昂,不肯定性大,沒法保證各個對象的調用順序。 finalize()能作的全部工做使用try-finally或者其餘方式均可以作的更好,徹底能夠忘記在這個函數的存在。

方法區的回收

  • 在方法區進行垃圾回收的性價比通常比較低。 主要回收兩部分,廢棄常量和無用的類。

知足無用的類三個判斷條件才僅僅表明能夠進行回收,不是必然關係,可使用-Xnoclassgc參數控制。

  1. 該類的全部實例都已經被回收,也就是Java堆中不存在該類的任何實例。
  2. 加載該類的ClassLoader已經被回收。
  3. 該類對應的java.lang.Class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問到該類的方法。

引用類型

    1. 強引用
    • 使用new一個新對象的方式來建立強引用。 只要強引用還存在,被引用的對象則永遠不會被回收。
    1. 軟引用
    • 使用SoftReference類來實現軟引用。 用來描述一些還有用可是並不是必須的對象,被引用的對象在將要發生內存溢出異常以前會被回收。
    1. 弱引用
    • 使用WeakReference類來實現弱引用。 強度比軟引用更弱一些,被引用的對象在下一次垃圾收集時會被回收。
    1. 虛引用
    • 使用PhantomReference類來實現虛引用。 最弱的引用關係,不會對被引用的對象生存時間構成影響,也沒法經過虛引用來取得一個對象實例。 惟一目的就是能在這個對象被收集器回收時收到一個系統通知。

垃圾收集算法

    1. 標記 - 清除
    • 首先標記出全部須要回收的對象,在標記完成後統一回收被標記的對象並取消標記。

不足:

  1. 效率問題,標記和清除兩個過程的效率都不高。
  2. 空間問題,標記清除以後會產生大量不連續的內存碎片,沒有連續內存容納較大對象而不得不提早觸發另外一次垃圾收集。
    1. 標記 - 整理
    • 和標記 - 清除算法同樣,但標記以後讓全部存活對象都向一段移動,而後直接清理掉端邊界之外的內存。 解決了標記 - 清除算法的空間問題,但須要移動大量對象,仍是存在效率問題。
    1. 複製
    • 將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用多的內存空間一次清理掉。 代價是將內存縮小爲原來的通常,過高了。

如今商業虛擬機都採用這種算法用於新生代。 由於新生代中的對象98%都是朝生暮死,因此將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor空間。 當回收時,若是另一塊Survivor空間沒有足夠的空間存放存活下來的對象時,這些對象將直接經過分配擔保機制進入老年代。

    1. 分代收集
    • 通常把Java堆分爲新生代和老年代。 在新生代中使用複製算法,在老年代中使用標記 -清除 或者 標記 - 整理 算法來進行回收。

HotSpot的算法實現

  • 枚舉根節點(GC Roots)

    • 目前主流Java虛擬機使用的都是準確式GC。 GC停頓的時候,虛擬機能夠經過OopMap數據結構(映射表)知道,在對象內的什麼偏移量上是什麼類型的數據,並且特定的位置記錄着棧和寄存器中哪些位置是引用。所以能夠快速且準確的完成GC Roots枚舉。
  • 安全點

    • 爲了節省GC的空間成本,並不會爲每條指令都生成OopMap,只是在「特定的位置」記錄OopMap,這些位置稱爲安全點。

程序執行只有到達安全點時才能暫停,到達安全點有兩種方案。

  1. 搶斷式中斷(幾乎不使用)。GC時,先把全部線程中斷,若是有線程不在安全點,就恢復該線程,讓他跑到安全點。
  2. 主動式中斷(主要使用)。GC時,設置一個標誌,各個線程執行到安全點時輪詢這個標誌,發現標誌爲直則掛起線程。

可是當線程sleep或blocked時沒法響應JVM的中斷請求走到安全點中斷掛起,因此引出安全區域。

  • 安全區域

    • 安全區域是指在一段代碼片斷之中,引用關係不會發生變化,是擴展的安全點。

線程進入安全區域時表示本身進入了安全區域,這個發生GC時,JVM就不須要管這個線程。 線程離開安全區域時,檢查系統是否完成GC過程,沒有就等待能夠離開安全區域的信號爲止,否者繼續執行。

垃圾收集器

  • 新生代

      1. serial收集器
      • 它是單線程收集器,只會使用一個線程進行垃圾收集工做,更重要的是它在進行垃圾收集時,必須暫停其餘全部的工做線程。

優勢:對比其餘單線程收集器簡單高效,對於單個CPU環境來講,沒有線程交互的開銷,所以擁有最高的單線程收集效率。

它是Client場景下默認新生代收集器,由於在該場景下內存通常來講不會很大。

- 2. parnew收集器

	- 它是Serial收集器的多線程版本,公用了至關多的代碼。
複製代碼

在單CPU環境中絕對不會有比Serial收集器更好的效果,甚至在2個CPU環境中也不能百分之百超越。

它是Server場景下默認的新生代收集器,主要由於除了Serial收集器,只用它能與CMS收集器配合使用。

- 3. parallel scavenge收集器

	- 「吞吐優先」收集器,與ParNew收集器差很少。
複製代碼

可是其餘收集器的目標是儘量縮短垃圾收集時用戶線程停頓的時間,而它的目標是達到一個可控制的吞吐量。這裏的吞吐量指CPU用於運行用戶程序的時間佔總時間的比值。

  • 老年代

      1. serial old收集器
      • 是Serial收集器老年代版本。

也是給Client場景下的虛擬機使用的。

- 5. parallel old收集器

	- 是Parallel Scavenge收集器的老年代版本。
複製代碼

在注重吞吐量已經CPU資源敏感的場合,均可以優先考慮Parallel Scavenge和Parallel Old收集器。

- 6. cms收集器

	- Concurrent Mark Sweep收集器是一種以獲取最短回收停頓時間爲目標的收集器。
	- 運做過程

		- 1. 初始標記(最短)。仍須要暫停用戶線程。只是標記一下GC Roots能直接關聯到的對象,速度很快
複製代碼
  1. 併發標記(耗時最長)。進行GC Roots Tracing(根搜索算法)的過程。
  2. 從新標記。修正併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄。比初始標記長但遠小於併發標記時間。
  3. 併發清除

1 和4 兩個步驟並無帶上併發兩個字,即這兩個步驟仍要暫停用戶線程。

- 優缺點

		- 併發收集、低停頓。
複製代碼
  1. CMS收集器對CPU資源很是敏感。雖然不會致使用戶線程停頓,可是佔用CPU資源會使應用程序變慢。
  2. 沒法處理浮動垃圾。在併發清除階段新垃圾還會不斷的產生,因此GC時要控制「-XX:CMSinitiatingOccupancyFraction參數」預留足夠的內存空間給這些垃圾,當預留內存沒法知足程序須要時就會出現」Concurrent Mode Failure「失敗,臨時啓動Serial Old收集。
  3. 因爲使用標記 - 清除算法,收集以後會產生大量空間碎片。
    1. g1收集器
    • Garbage First是一款面向服務端應用的垃圾收集器

    • 運做過程

        1. 初始標記
  1. 併發標記
  2. 最終標記
  3. 刪選標記

5、類加載機制

概述

  • 虛擬機把描述類的數據從Class問價加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型。 Java應用程序的高度靈活性就是依賴運行期動態加載和動態鏈接實現的。

類的生命週期

  • 加載 -> 鏈接(驗證 -> 準備 -> 解析) -> 初始化 -> 使用 - >卸載

類初始化時機

  • 主動引用

    • 虛擬機規範中沒有強制約束什麼時候進行加載,可是規定了有且只有五種狀況必須對類進行初始化(加載、驗證、準備都會隨之發生)
  1. 遇到new、getstatic、putstatic、invokestatic這四條字節碼指令時沒有初始化。
  2. 反射調用時沒有初始化。
  3. 發現其父類沒有初始化則先觸發其父類的初始化。
  4. 包含psvm(mian()方法)的那個類。
  5. 動態語言支持時,REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄。
  • 被動引用

    • 除上面五種狀況以外,全部引用類的方式都不會觸發初始化,稱爲被動引用。
  1. 經過子類引用父類的靜態字段,不會致使子類的初始化。
  2. 經過數組定義來引用類,不會觸發此類的初始化。該過程會對數組類進行初始化,數組類是一個由虛擬機自動生成的、直接繼承Object的子類,其中包含數組的屬性和方法,用戶只能使用public的length和clone()。
  3. 常量在編譯階段會存入調用類的常量池中,本質上並無直接引用到定義常量的類,所以不會觸發定義常量的類的初始化。

類加載過程

    1. 加載
      1. 經過類的全限定名來獲取定義此類的二進制字節流。
  1. 將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。
  2. 在內存中生成一個表明這個類的java.lang.Class對象(HotSpot將其存放在方法區中),做爲方法區這個類的各類數據的訪問入口。
    1. 驗證
    • 爲了確保Class文件的字節類中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。能夠經過-Xverify:none關閉大部分類驗證。
  1. 文件格式驗證。確保輸入字節流能正確的解析並存儲於方法區,後面的3個驗證所有基於方法區的存儲結構進行,不會再操做字節流。
  2. 元數據驗證。對字節碼描述信息進行語義分析,確保其符合Java語法規範。(Java語法驗證)
  3. 字節碼驗證。最複雜,經過數據流和控制流分析,肯定程序語義時合法的、符合邏輯的。能夠經過參數關閉。(驗證指令跳轉範圍,類型轉換有效等)
  4. 符號引用驗證。將符號引用轉化爲直接引用,發生在第三個階段——解析階段中發生。
    1. 準備
    • 類變量是被static修飾的變量,準備階段爲類變量分配內存並設置零值(final直接設置初始值),使用的是方法區的內存。
    1. 解析
    • 將常量池內的符號引用替換爲直接引用的過程。 其中解析過程在某些狀況下能夠在初始化階段以後再開始,這是爲了支持Java的動態綁定。 解析動做主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄、和調用點限定符。
    1. 初始化
    • 初始化階段才真正執行類中定義的Java程序代碼,是執行類構造器()方法的過程。 在準備階段,類變量已經給過零值,而在初始化階段,根據程序員經過程序制定的主觀計劃去初始化類變量和其餘資源。

      • ()

        • 類構造器方法。是由編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊中的的語句合併產生的。
  1. 不須要顯式調用父類構造器,JVM會保證在子類clinit執行以前,父類的clinit已經執行完成。

  2. 接口中不能使用靜態語句塊但仍能夠有類變量的賦值操做。當沒有使用父接口中定義的變量時子接口的clinit不須要先執行父接口的clinit方法。接口的實現類也不會執行接口的clinit方法。

  3. 虛擬機會保證clinit在多線程環境中被正確的加鎖、同步。其餘線性喚醒以後不會再進入clinit方法,同一個類加載器下,一個類型只會初始化一次。

    - <init>()
    
     	- 對象構造器方法。Java對象被建立時纔會進行實例化操做,對非靜態變量解析初始化。
    複製代碼
  4. 會顯式的調用父類的init方法,對象實例化過程當中對實例域的初始化操做所有在init方法中進行。

類(加載) 器

  • 類與類加載器

    • 類加載器實現類的加載動做。 類加載器和這個類自己一同確立這個類的惟一性,每一個類加載器都有獨立的類命名空間。在同一個類加載器加載的狀況下才會有兩個類相等。 相等包括類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()、instanceof關鍵字。
  • 類加載器分類

    • 啓動類加載器

      • 由C++語言實現,是虛擬機的一部分。負責將JAVA_HOME/lib目錄中,或者被-Xbootclasspath參數指定的路徑,可是文件名要能被虛擬機識別,名字不符合沒法被啓動類加載器加載。啓動類加載器沒法被Java程序直接引用。
    • 擴展類加載器

      • 由Java語言實現,負責加載JAVA_HOME/lib/ext目錄,或者被java.ext.dirs系統變量所指定的路徑中的全部類庫,開發者能夠直接使用擴展類加載器。
    • 應用程序類加載器

      • 因爲這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也稱他爲系統類加載器。負責加載用戶類路徑(ClassPath)上所指定的類庫,通常狀況下這個就是程序中默認的類加載器。
    • 自定義類加載器

      • 由用戶本身實現。
  1. 若是不想打破雙親委派模型,那麼只須要重寫findClass方法便可。
  2. 不然就重寫整個loadClass方法。
  • 雙親委派模型

    • 雙親委派模型要求除了頂層的啓動類加載器外,其他的類加載器都應該有本身的父類加載器。父子不會以繼承的關係類實現,而是都是使用組合關係來服用父加載器的代碼。 在java.lang.ClassLoader的loadClass()方法中實現。

    • 工做過程

      • 一個類加載器首先將類加載請求轉發到父類加載器,只有當父類加載器沒法完成(它的搜索範圍中沒有找到所須要的類)時才嘗試本身加載
    • 好處

      • Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係,從而使得基礎類庫獲得贊成。

4、內存分配與回收策略

Minor GC 和 Full GC

  • Minor GC

    • 發生在新生代的垃圾收集動做,由於新生代對象存活時間很短,所以Minor GC會頻繁執行,執行速度快。

    • 時機

      • Eden不足
  • Full GC

    • 發生在老年區的GC,出現Full GC時每每伴隨着Minor GC,比Minor GC慢10倍以上。

    • 時機

        1. 調用System.gc()
        • 只是建議虛擬機執行Full GC,可是虛擬機不必定真正去執行。 不建議使用這種方式,而是讓虛擬機管理內存。
        1. 老年代空間不足
        • 常見場景就是大對象和長期存活對象進入老年代。 儘可能避免建立過大的對象以及數組,調大新生代大小,讓對象儘可能咋新生代中被回收,不進入老年代。
        1. JDK1.7 以前方法區空間不足
        • 當系統中要加載的類、反射的類和常量較多時,永久代可能會被佔滿,在未配置CMS GC的狀況下也會執行Full GC,若是空間仍然不夠則會拋出OOM異常。 可採用增大方法區空間或轉爲使用CMS GC。
        1. 空間分配擔保失敗
        • 發生Minor GC時分配擔保的兩個判斷失敗
        1. Concurrent Mode Failure
        • CMS GC 併發清理階段用戶線程還在執行,不斷有新的浮動垃圾產生,當預留空間不足時報Concurrent Mode Failure錯誤並觸發Full GC。

內存分配策略

    1. 對象優先在Eden分配
    • 大多數狀況下,對象在新生代Eden上分配,當Eden空間不夠時,發起Minor GC,當另一個Survivor空間不足時則將存活對象經過分配擔保機制提早轉移到老年代。
    1. 大對象直接進入老年代
    • 配置參數-XX:PretenureSizeThreshold,大於此值得對象直接在老年代分配,避免在Eden和Survivor之間的大量內存複製。
    1. 長期存活對象進入老年代
    • 虛擬機爲每一個對象定義了一個Age計數器,對象在Eden出生並通過Minor GC存活轉移到另外一個Survivor空間中時Age++,增長到默認16則轉移到老年代。
    1. 動態對象年齡綁定
    • 虛擬機並非永遠要求對象的年齡必須到達MaxTenuringThreshold才能晉升老年代,若是在Survivor中相同年齡全部對象大小總和大於Survivor空間的一半,則年齡大於或等於該年齡的對象直接進入老年代。
    1. 空間分配擔保
    • 在發生Minor GC以前,虛擬機先檢查老年代最大可用的連續空間是否大於新生代的全部對象,若是條件成立,那麼Minor GC能夠認爲是安全的。 能夠經過HandlePromotionFailure參數設置容許冒險,此時虛擬機將與歷代晉升到老年區對象的平均大小比較,仍小於則要進行一次Full GC。 在JDK1.6.24以後HandlePromotionFailure已無做用,即虛擬機默認爲true。
相關文章
相關標籤/搜索