Java 虛擬機 跨平臺
虛擬機隱藏平臺差異,解決不同平臺代碼運行結果不一致問題,實現Write Once, Run Anywhere
,實現用戶代碼跨平臺。它本身是一個操作系統上的應用程序,將字節碼文件翻譯成特定機器的機器碼。
Java 虛擬機 運行時內存區域
虛擬機隱藏內存區域,自動實現內存分配與垃圾回收,使用戶專注邏輯實現。
JVM 將運行時數據區分成五塊:堆,方法區,虛擬機棧,本地方法棧,程序計數器。
堆是內存區域最大的一塊,存放對象實例。所有線程共享。它是垃圾回收器 GC 主要管理的區域。堆中也存在線程私有區域(Thread Local ALlocation Buffer)以提升效率。Java 參數 -Xmx -Xms 可以設定 Java 堆大小。
方法區也是線程共享區域。相對于堆,垃圾回收沒有那么頻繁,條件也更苛刻。它存放存儲虛擬機加載的類型信息,常量,靜態變量,即時編譯器代碼緩存。垃圾回收主要針對常量池和類型卸載。JVM 將字節碼文件中的字面量,符號引用放入運行時常量池。內存不足也報OOM。
虛擬機棧對應線程。每個線程一個棧,且二者生命周期相同。線程執行的方法對應棧幀。棧幀保存局部變量表,操作數棧,返回地址等。調用和返回方法對于棧幀入棧和出棧。如果無限遞歸,棧深度超出閾值,報StackOverflowError。如果內存不足,OOM。
局部變量表存放方法所需的局部變量和返回地址。對于基本類型,直接存放值,對于引用類型,存放指針。局部變量表基本單位是四字節的 slot。小于四字節的 boolean,byte,char,short 變量也占據一個 slot。
本地方法棧與虛擬機棧類似,執行的是 native 方法,即非 Java 方法。
程序計數器指示當前線程執行字節碼行號,用于控制程序流程。線程上下文切換期間記錄當前線程執行行號,下次獲取時間片后從當前行號繼續執行。程序計數器不會內存溢出,線程私有。
直接內存不是虛擬機運行數據區一部分,它不由 JVM 管理,不受 JVM 大小限制。NIO 使用本地方法分配堆外內存(即直接內存,機器直接分配的內存),通過堆內的 DirectByteBuffer 對象引用堆外內存,避免數據在堆內堆外來回復制。
對象的創建
第一步,要求虛擬機已經加載類。
第二步,為對象分配內存。類加載時就確定大小。分配內存的方法有:指針碰撞和空閑列表。指針碰撞:將堆分為兩塊,一塊已分配,另一塊未分配,指針為邊界,兩塊不能有交錯。移動指針分配空空閑內存。空閑列表:已分配與未分配交錯,維護一個列表,記錄可用列表塊。虛擬機采用CAS+重試實現線程安全地分配內存。
第三步,初始化內存,將實例字段初始化為零值。
第四步,設置對象頭。
第五步,執行構造函數。
對象布局
對象在堆內存中分三塊:對象頭,實例數據,對齊填充。對象頭包含兩部分:對象運行時數據和類型指針。運行時數據包括:GC 年齡,哈希碼,鎖狀態。類型指針指向類型元數據。如果是對象是數組,還包含數組長度。
實例數據包含對象字段。父類定義的變量在子類之前,相同長度的字段總是分配在一起。
對齊填充:對象起始地址必須是八字節的整數倍。