Java虛擬機總結給面試的你(上)

Java虛擬機一直是Java的重難點,一方面因爲系統封裝得太好,你日常寫程序的時候幾乎感受不到它的存在,另外一方面瞭解必要的Java虛擬機工做原理才能對真實工做環境下的bug進行對症下藥,另外虛擬機這一部分也一直是面試考官愛問的問題。因而這篇博客就針對Java虛擬機的各個知識點進行概括。程序員

一.Java內存區域

運行時數據區域

程序計數器

程序計數器是當前線程執行的字節碼的行號指示器,線程私有,獨立存儲面試

Java虛擬機棧

Java虛擬機棧是線程私有,與Java的方法執行模型有關,描述Java方法執行的內存模型:方法執行時建立棧幀用於儲存局部變量表等信息,方法調用返回對應棧幀再虛擬機中的入棧出棧。算法

既然是棧那麼深度就是必定的,若線程請求棧深度大於虛擬機所規定的深度,則拋出StackOverflowError異常。若虛擬機棧請求擴展時沒法申請到足夠的內存,則拋出OOM異常。數組

本地方法棧

就是Native方法所用到的棧,與虛擬機棧做用相似。佈局

Java堆

Java堆是被全部線程共享的一塊內存區域,屬於線程共享區,在虛擬機啓動時建立。它主要做用是存放對象實例和進行垃圾收集管理。編碼

方法區

方法區也是各個線程共享的內存區域,用於儲存已被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼等數據。線程

運行時常量池

運行時常量池其實屬於方法區,它主要用於存放編譯期生成的各類字面量和符號引用,而且具備動態的特色。翻譯

new關鍵字的建立流程

  1. 檢查指令的參數可否在常量池中定位到一個類的符號引用
  2. 檢查是否已經加載解析和初始化
  3. 從Java堆中劃份內存給新生對象,使用CAS保證分配的原子性
  4. 將內存空間初始化爲零值
  5. 對對象進行設置,存放在對象頭中
  6. 執行方法,按照程序員的意願進行初始化

分配方式

  1. 指針碰撞指針

    若Java堆中的內存都是規整的,用過的內存都在左邊,沒用過的都在右邊,中間指針指向臨界點,分配內存就很簡單,只用把指針往右移動和待分配對象同樣的內存區域就好了。cdn

  2. 空閒列表

    若是內存不是規整的,用過的和沒用過的內存交錯在一塊兒,就不能使用指針碰撞了,須要維護一個列表記錄可用的內存塊,分配內存時就從列表中找一塊足夠大的內存記錄下來。

對象的內存佈局

對象頭

儲存對象自身的運行時數據,eg:哈希碼,GC分代年齡,鎖狀態標誌等。還有類型指針指向它的類元數據的指針,經過這個指針肯定這個對象是哪一個類的實例。如果Java數組則對象頭還有一塊記錄數組長度的數據。

實例數據

程序代碼中所定義的各類類型的字段內容,相同寬度的字段分配到一塊兒

對象訪問定位

虛擬機經過棧上的reference數據來操做堆上的具體對象。

訪問方式

  1. 使用句柄

    包含對象實例數據與類型數據各自的地址信息,reference中儲存的就是對象的句柄地址。句柄地址穩定,對象移動時只改變句柄中的實例數據指針,reference自己不修改。

  2. 直接指針

    reference中儲存的就是對象地址,速度更快

二.垃圾收集器與內存分配策略

引用計數算法

給對象添加一個引用計數器,有一個地方引用它時,計數器值就加一,引用失效時就減一,任什麼時候刻計數值爲0的對象就死了。這個算法雖然簡單可是有一個致命的缺點就是沒法解決對象之間相互循環引用的關係。可達性分析算法應運而生。

可達性分析算法

GC Roots做爲起點向下搜索,若一個對象到GC Roots沒有引用鏈的話,則證實此對象不可用,能夠回收。搜索的對象有:

  • 虛擬機棧中引用的對象
  • 方法區中靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中Native 方法引用的對象

對象的回收經歷

對象在沒有引用鏈通往GC Roots時,須要通過兩次標記才能真正死亡。

  1. 對象在進行可達性分析後若是沒有與GC Roots相鏈接的引用鏈,會被第一次標記並篩選,若對象沒有覆蓋finalize方法或者已經調用過了則不會調用finalize。若是須要調用finalize方法,則對象被放在F-Queue隊列中,等待線程執行。
  2. 對象若是想存活下去,finalize方法是最後的機會,不然GC對F-Queue隊列進行第二次標記後對象真正死亡。

垃圾回收算法

標記-消除算法

首先標記出全部須要回收的對象,在標記完成後統一回收,缺點是效率低下並且產生大量的內存碎片。

複製算法

將內存劃分爲大小相等的兩塊,每次只使用其中的一塊,當這一塊的內存用完了,就將還存活的對象複製到另一塊上面,而後把已經使用的內存空間一次清理掉。缺點是將內存縮小爲了原來的一半,代價較高,對象存活率較高時效率低。

HotSpot實際使用(回收新生代)則是將內存劃分爲較大的Eden區和兩塊較小的Survivor區,一塊Eden區和一塊Survivor區大小比例爲8:1,垃圾回收時就將Eden區和已使用的Survivor區中還存活的對象移到另外一塊Survivor區中,因爲根據統計,98%的對象都是很快死亡的,因此按照8:1:1的比例來劃份內存明顯比1:1劃份內存效率要高不少。

標記-整理算法

標記出須要回收的對象,而後讓全部存活的對象都向一段移動,將另外一端的內存區域清除掉。

分代收集算法

根據新生代和老年代的不一樣特色選擇不一樣的算法,新生代使用複製算法,老年代使用標記清楚或標記整理算法,虛擬機實際使用這種算法。

內存分配與回收策略

對象優先在Eden上分配

GC分類

  1. Monior GC,新生代GC,指發生在新生代的垃圾收集動做,由於Java對象大多都具有朝生夕滅的特色,因此Monior GC很頻繁,速度也很快
  2. Major GC/Full GC,老年代GC,指發生在老年代的垃圾回收動做,通常比Monior GC慢十倍以上。

大對象直接進入老年代

大對象指須要大量連續內存空間的Java對象,如很長的字符串以及數組。直接進入老年代避免頻繁的GC活動。

長期存活的對象將進入老年代

對象在新生代區域每熬過一次Minor GC,年齡就增長一歲(Age Count),超過15歲(默認),就會被晉升到老年代中。

動態年齡斷定

若是相同年齡的對象所佔內存大於Survivor空間的一半,年齡大於等於該年齡的對象就能夠直接進入老年代。

三.類文件結構

Class類文件的結構

一組以八位字節爲基礎的二進制流,各個數據項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有任何分隔符。

儲存結構

無符號數,用來描述數字,索引引用,數量值或UTF-8編碼的字符串

,多個無符號數+表=表,_info結尾,Class實際上就是一張表

魔數

每一個Class文件的頭4個字節,肯定這個文件是否爲一個能被虛擬機接受的Class文件。class文件的魔數是0XCAFEBABE。

Class文件的版本號

緊跟魔數的四個字節肯定版本號:5,6字節爲次版本號,7,8字節爲主版本號。jdk向下兼容,不向上兼容。

常量池

緊隨主次版本號以後包含:

  • 字面量文本字符串,申明爲final的常量值。
  • 符號引用
    • 類和接口的全限定名
    • 字段的名稱和描述符
    • 方法的名稱和描述符
  • 動態鏈接各個字段的內存信息,從常量池中得到對應的讀出引用,再在類建立時或運行解析翻譯到具體的內存地址之中。
  • 每一項常量都是一個表,每一個表的第一位都是一個是一個u1類型的標誌位,表明這個常量屬於哪一種常量類型。

訪問標誌

緊隨常量池後面,兩個字節表明訪問標誌,標識類或接口的訪問信息。如這個Class是類仍是接口,public類型等。

類索引,父類索引,接口索引集合

除了接口索引是集合外,其餘索引都只有一個,用這三個索引肯定類的繼承關係。類索引用於肯定類的全限定名,父類索引用於肯定父類的全限定名。

字段表集合

用於描述類或接口中聲明的變量,字段包括類級變量和實例級變量,不包括方法中聲明的局部變量,描述字段的屬性如public,static,final等用一個布爾變量表示,恰好使用一個標誌位,經過引用常量池中的常量來肯定。

方法表集合

與字段表類似。

屬性表集合

Class文件,字段表,方法表均可以攜帶本身的屬性表集合,用於描述某些場景專有的信息。

字節碼指令

操做碼長度爲一個字節,因此總數最多不超過256條。。

相關文章
相關標籤/搜索