深入理解java虛擬機

java虛擬機

java概述

  • java爲什麼是跨平臺的?是如何實現跨平臺的?

java的目標是"一次編譯,處處運行",就是說,java源碼經過編譯後可以在無論是windows還是linux下都能運行.

  • java實現跨平臺的原理是什麼?

java實現跨平臺的原理和C不同,java是使用編譯器編譯之後生成字節碼文件(*.class),然後各種不同平臺的虛擬機與所有平臺都統一使用這種字節碼,然後虛擬機將描述類的數據從class文件中加載到內存中,並對數據進行校驗,轉換解析和初始化,最終形成可以被虛擬機直接使用的java類型

  • c語言編譯運行過程是

gcc -o a a.c 會生成一個可執行二進制文件a,不同平臺下使用不同的編譯器生成不同的可執行文件,這些文件不是跨平臺的,所以c程序要想在不同平臺下執行,都是要重新編譯的然後生成該平臺下的可執行文件才行的.

java程序是如何執行的?

  • java編譯器編譯產生class文件
  • class加載到jvm進行執行

jvm分類

jvm內存分爲:

操作數棧,局部變量表,java堆,常量池,方法區

java內存區域與內存溢出異常

運行時數據區域

包括:

  • 程序計數器

當前線程所執行字節碼的行號指示器,字節碼解釋器工作時就是通過改變程序計數器的值來選取下一條需要執行的字節碼指令
由於java虛擬機的多線程實現是通過線程輪流切換,並分配處理器執行時間的方式來實現,在任何一個確定的時刻,一個處理器都只會執行一條線程中的指令.
每條線程都有一個獨立的程序計數器,各個線程的程序計數器互不影響,獨立存儲,被稱爲線程私有的內存.
如果線程執行java方法,這個程序計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果執行的是nativate方法,則計數器值爲空.
此內存區域是唯一一個在java虛擬機規範中沒有規定任何OutOfMemoryError情況的區域.

  • java虛擬機棧

線程私有的
生命週期和線程相同
虛擬機棧描述的是java方法執行的內存模型;每個方法執行的同時都會創建一個棧幀用於存儲局部變量表,操作數棧,動態鏈接,方法出口等
這個也是我們常說的java棧內存
局部變量表存放的是編譯器可知的各種基本數據類型,對象引用類型,returnAddress類型
64爲long和double類的數據都會佔用2個局部變量空間,其他的數據類型只佔用1個局部變量空間
局部變量表所需的內存是在編譯期間完成分配的.當進入一個方法時,這個方法需要在幀中分配多大的內存空間是確定的,這個方法在運行期間不會改變局部變量表的大小.
異常種類:
StackOverflowError:線程請求的棧深度大於虛擬機允許的棧深度
OutOfMemoryError: 擴展時無法申請足夠的內存

  • 本地方法棧

與java虛擬機棧相似,虛擬機棧執行java方法服務,本地方法棧執行Native方法服務
異常種類和虛擬機棧相同

  • java堆

java虛擬機所管理的內存最大的一塊,java堆是被所有線程所共享的的一塊內存區域,在虛擬機啓動的時候創建.此內存區域的唯一目的是存放對象實例,幾乎所有的對象實例都在這裏分配內存,所有的對象實例和數組都要在堆上分配內存.
java堆也是java垃圾收集器管理的主要區域,很多時候也被成爲GC堆. 從內存回收的角度來看,線程共享的java堆可能劃分出多個線程私有的緩衝區.
java堆可以存放在物理上不連續的內存空間中
可以通過-Xmx和-Xms控制,如果堆中沒有內存完成實例分配,並且堆無法擴展,將會拋出OutOfMemoryError異常

  • 方法區

是線程共享的內存區,用戶存儲已被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼等數據
當方法區無法滿足內存分配需求時,會拋出OutOfMemoryError

  • 運行時常量池

運行時常量池是方法區的一部分,class文件中除了有類的版本,字段,方法接口等信息外,還有一項信息是常量池,用於存放編譯期生成的字面量和符號引用,這部分將在類加載後進入方法區的運行時常量池中存放
常量池無法申請到內存時也會拋出OutOfMemoryError.

  • 直接內存

它可以使用Native函數直接分配堆外內存,然後通過一個存儲在java堆中的DirectByteBuffer對象作爲這一塊對象的引用操作

Hotspot虛擬機對象

虛擬機遇到一條new指令時,首先檢查這個指令的參數是否能夠在常量池中定位到一個類的符號引用,並檢查這個符號引用代表的類是否被加載過,解析和初始化過.如果沒有必須先執行相應的加載過程.在類加載檢查通過後,接下來虛擬機將爲新生對象分配內存,對象所需的內存大小在加載完成之後便確定了.

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

哪些內存需要回收?

首先,使用引用計數算法,對每一個對象被引用次數進行統計的話,如果對象之間循環引用的話而這兩個對象又沒有被用過,這種情況下,該算法的引用次數不爲0,就無法通過GC收集器回收它們.

可達性分析算法

通過一系列的GC Roots的對象作爲起點,從這些節點向下搜索,搜索所走過的的路徑被稱爲引用鏈,當一個GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的.

  • 作爲GC Roots的對象包括:

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

  • 強引用

永遠都不會被回收

  • 軟引用

還有用,但並非是必需的對象

  • 弱引用

非必需對象,強度比軟引用更弱

  • 虛引用

它是最弱的引用關係,唯一目的是能在這個回收器回收時收到一個系統通知.

什麼時候回收?

如何回收?

標記-清除算法

  • 標記所有需要回收的對象,在標記完成後統一回收所有被標記的對象.
    問題:
    標記和清除效率不高,標記清除後會產生大量不連續的內存碎片

複製算法

  • 將內存劃分爲大小相同的兩塊,當這一塊用完了,將存活的拷貝到另外一塊上,清理掉已經使用過的內存塊,重新緊密排列,不會產生碎片.
  • 問題:
    複製收集算法在對象存活率過高時,就要進行較多的複製操作,效率就會變低.

標記整理算法

  • 過程和標記清除算法相同,但後續步驟不同,不是直接對可回收對象進行清理,而是將所有存活對象向一端移動,然後直接清除掉端邊界以外的內存.

分代收集算法

  • 根據對象存活週期的不同將內存劃分爲幾塊,一般將java堆劃分爲新生代和老年代,這樣可以在新生代使用複製算法,會stop the world,因爲新生代每次回收的時候都會有大量的對象死去.而老年代因爲經過多輪淘汰,對象的存活率比較高,沒有額外的空間對它進行分配擔保,就必須使用標記清除算法,或者標記整理算法進行回收,有可能會stop the world.
  • Hotspot虛擬機的分代回收,分爲一個Eden區,兩個Survivor區以及Old Generation,Eden以及Survivor共同組成New Generatiton.
  • 新生代回收Minor GC
  • 老年代回收稱爲Major GC,除併發GC外均需堆整個堆以及Permanent Generation進行掃描和回收,因此又被稱爲Full GC

HotSpot的算法實現

枚舉根節點算法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章