Java虛擬機

內存分配以及回收

Java虛擬機運行時數據區,分爲以下幾個模塊,包含所有線程共有的數據區和線程單獨享有的數據區。
Java虛擬機

  1. 程序計數器:字節碼行號,通過這個計數器來選取下一條需要執行的指令,線程獨有。
  2. 虛擬機棧:線程私有。方法在執行時會創建一個棧幀,用於存儲局部變量表等。局部變量表中存放了編譯器可知的基本數據類型、對象引用、returnAddress(指向了一條字節碼指令的地址)
  3. 本地方法棧:與虛擬機棧類似,只不過這個地方是爲native方法服務。
  4. 堆:線程共用。存放對象實例。
  5. 方法區:線程共用。存儲已經被虛擬機加載的類信息、常量、靜態變量等。
  6. 運行時常量池:用於存放編譯期生成的字面量和符合引用。字面量就是我們所說的常量概念,如文本字符串、被聲明爲final的常量值等。符號引用是一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可,一般包括下面三類常量:類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符。

Java虛擬機
JVM通過根搜索算法來判定對象是否可以回收,一般對於不能從根(GC Roots)搜索到的對象是可以被回收的。
能夠被作爲GC Roots對象有:虛擬機棧本地變量表中引用的對象(也就是正在調用的方法中引用的);方法區中靜態屬性或常量引用的對象;本地方法棧引用的對象。


可以被回收的對象並不一定絕對被回收,JVM先做一次標記和篩選,把那些覆蓋了finalize方法的對象篩選出來然後觸發finalize方法,如果在finalize方法中對象復活,則不回收,否則回收,且finalize方法僅會被觸發一次。


垃圾回收算法

  1. 標記-清除:把標記爲待回收的對象空間清除,容易造成大量空間碎片;
  2. 複製算法:將內存分爲三個區域,一個較大的eden區和兩個較小的survivor區。每次GC都把存活的對象挪到其中一個servivor區,然後把eden全部清除。只對每次GC時存活對象較少時比較有效,適用於新生代;
  3. 標記-整理:把標記後存活的對象向一個方向移動,然後清除其它空間。比較適合老年代。

內存分配與回收策略

  1. 對象默認優先分配在新生代;
  2. 大對象直接分配到老年代;
  3. 長期存活的對象轉移到老年代:虛擬機給每個對象定義一個對象年齡,沒發生一次minor GC,年齡就增加一次,超過默認值之後就會進入到老年代。
  4. 動態對象年齡判定:對象不一定是必須到了默認年齡才能進入老年代,如果一個eden區中所有相同年齡的對象大小綜合超過eden一半的空間,那麼大於等於這個年齡的對象也會進入老年代。

類文件結構

class文件是二進制組成的,class有兩種數據類型:無符號數和表。
無符號數是基礎數據類型,其中u1表示1個字節、u2表示2個字節(一個字節8個bit,而4個bit可以表示1個16進制的數,也就是說1個字節可以用2個16進制數表示);
表是由多個無符號數或其它表構成的。
Java虛擬機

  1. magic是4個字節,也就是8個16進制數,固定爲CAFEBABE;後面分別是兩個版本號。
  2. 常量池:跟着版本號之後的就是常量池(字面量和符號引用)。由於無法確認一個類中常量池有多少常量,所有先有一個值來標誌有多少個,然後再是常量具體信息。
  3. 訪問標誌:常量池之後跟着的是2個字節的訪問標誌。需要被標誌的內容包括:是否public、是否final、是否abstract、是類或接口
  4. 訪問標誌之後是類索引(用於確定該類的全限定名)、父類索引(用於確定父類的全限定名)、接口所有集合(實現的接口可能不止一個)
  5. 字段表集合:描述接口或類中聲明的變量,包含類變量和實例變量。
  6. 方法表集合:描述類或接口中聲明的fangfa。
  7. 屬性表結合:
    code屬性:java方法體中的代碼經javac編譯後會存儲在code屬性中(接口中方法或抽象方法沒有code屬性)
    Exceptions屬性:列舉出方法throws後面拋出的異常;
    其它各屬性不再一一列舉。

類加載機制

Java虛擬機
類加載的時機
主動引用的幾種情況纔會加載(前提是此類沒有被加載過)

  1. new一個對象、引用類的static變量(final變量除外)、調用類的static方法;
  2. 對類進行反射調用時;
  3. 初始化一個類時,如果父類沒有被初始化,則先初始化父類;
  4. 虛擬機啓動時,初始化包含main方法的那個類

被動引用不會觸發初始化

  1. 調用父類靜態方法,不會初始化子類;
  2. 通過數組定義引用類,不會觸發初始化;
  3. 引用靜態常量不會觸發。

加載過程

  1. 通過一個類全限定名獲取定義此類的二進制字節流(一般是class文件)
  2. 將二進制字節流轉化爲方法區中的運行時數據結構
  3. 在內存(堆)中生成這個類的Class類的對象,作爲方法區這個類的各個數據的訪問入口

連接過程

  1. 驗證階段:文件格式驗證(是否符合Class文件規範)、元數據驗證(是否符合java語法規範)、字節碼驗證(確保語義是符合邏輯的)、符合引用驗證。
  2. 準備階段:正式爲類變量分配內存並設置初始值。
    有兩點需要注意:
    一,此處只爲類變量分配內存(static修飾的),不包含實例變量;
    二,設置的初始值是這個類型的0值,不是實際值(但被final修飾的賦的就是實際值)
  3. 解析階段:將符合引用替換爲直接引用

初始化過程
初始化過程主要是執行類構造器<cinit>方法

  1. <cinit>方法主要是手機所有類變量的賦值動作,和靜態語句塊(staic {});
  2. 虛擬機會保證<cinit>方法在父類中先調用,這樣說明父類的static語句塊要比子類的static變量賦值操作先執行,以下代碼中,字段B的值將會是2Java虛擬機
  3. 這也說明了一個問題:new一個對象時,靜態變量賦值和靜態語句塊會在類的構造方法前執行。

類加載器

  1. 比較兩個類對象是否相等,只有加載兩個類加載器的完全一樣,纔有意義;
  2. 如果一個類加載器收到一個類加載請求,它首先會請求委派給父類加載器完成,父類無法完成時,子類加載器才進行加載。

虛擬機字節碼執行引擎

Java虛擬機
運行時棧幀結構

  1. 局部變量表:存放方法參數和局部變量。每個變量以slot爲單位,slot可以複用Java虛擬機注意,如果沒有int a = 0這一行代碼,placeholder是不會被回收的,因爲如果不加這行代碼,就沒有任何對局部變量表的讀寫操作,這個slot就不會被佔用。
  2. 操作數棧:方法執行過程中,會有各種字節碼出棧入棧
  3. 動態鏈接:一部分符合引用在類加載時轉化爲直接引用,這是靜態機械;而一部分則是運行時轉化爲直接應用,這叫動態鏈接

方法調用和分派

  1. 所有的方法在Class文件中都是一個符合引用,而一部分方法在類加載時就直接解析爲直接引用。這種方法必須是“編譯時已知,運行時不可變”,就是靜態方法和私有方法兩大類
  2. 靜態分派:依賴靜態類型來定位方法執行版本稱爲靜態分派,典型應用是重載。Java虛擬機
    Human是靜態類型,後面的Man和Women則是實際類型。
    靜態類型在編譯器可知,而動態類型則是在運行時才能知道。
  3. 動態分派:運行期間根據實際類型來確定方法執行版本,典型應用是覆蓋。Java虛擬機
    結果是
    Java虛擬機

內存模型及線程安全

Java虛擬機
JMM規定所有內存都存儲於主內存中,每條線程還有自己的工作內存。
變量的讀取、賦值操作必須在工作內存中進行。
內存直接的交互操作,主要有以下8種操作:
Java虛擬機
Java虛擬機
8種操作需要滿足以下規則
Java虛擬機


volatile關鍵字

  1. volatile關鍵字保證了變量的所有線程的可見性,但並非是線程安全的。
    兩種情況下是線程不安全的:
    一,變量依賴於自身(比如i++之類的)
    二,變量依賴於其它變量(比如i=a+3)
  2. volatile禁止語義重排序
  3. volatile的具體實現
    Java虛擬機
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章