JVM學習筆記——初識JVM

午夜12點發表於2018-06-14

簡述

"Write Once,Run Anywhere"(一次編譯,到處執行)是sun宣傳Java語言所提出的口號。Java語言跨平臺的特性與Java虛擬機器的存在密不可分。Java原始碼通過編譯生成.class檔案位元組碼後再被JVM解釋轉化為目標機器程式碼,到處執行的關鍵與前提就是JVM。所以並不是Java語言本身可以可以跨平臺,而是在不同的平臺都有可以讓Java語言執行的環境而已。在可以執行Java虛擬機器的地方都內含一個JVM作業系統,從而使JAVA提供了各種不同平臺上的虛擬機器制,由此可見匯出執行的關鍵就是JVM。

JVM記憶體區域

JVM結構

JVM學習筆記——初識JVM

執行緒共享區域

  • ①Java堆是Java虛擬機器管理的記憶體中最大的一塊。
    ②堆中存放物件例項,幾乎所有的物件例項都在這裡分配記憶體,所以是GC主要區域。
    ③不需要連續的記憶體
    ④若堆中沒有記憶體完成例項分配,則會丟擲OutOfMemoryError異常

  • 方法區
  • ①儲存已被虛擬機器載入的類資訊(類名、訪問修飾符、欄位描述、方法描述等)、常量、靜態變數、即時編譯器編譯後的程式碼等資料
    ②執行時常量池是方法區的一部分
    ③這區域的記憶體回收目標主要是針對常量池的回收和對型別的解除安裝
    ④不需要連續的記憶體,方法區gc價效比較低,gc頻率遠沒有堆中高
    ⑤若方法區無法滿足記憶體分配需求時,則會丟擲OutOfMemoryError異常

    執行緒隔離區域(隨執行緒而生,隨執行緒而滅)

  • 虛擬機器棧
  • ①生命週期與執行緒相同,管理Java方法執行的記憶體模型
    ②方法執行建立棧幀(棧幀所需記憶體在類結構確定下來時就被已知,不考慮JIT優化)用於儲存區域性變數表、運算元棧、動態連結、方法出口等資訊
    ③棧的大小決定了方法呼叫的深度(遞迴多少層次,或巢狀呼叫多少層其他方法)
    ④若執行緒請求的棧深度大於虛擬機器所允許的深度,則丟擲StackOverflowError異常
    ⑤若棧中無憂記憶體可分配,則丟擲OutOfMemoryError異常

  • 本地方法棧
  • ①與虛擬機器棧功能類似,但管理的不是Java方法,而是本地native方法
    ②同樣也會出現StackOverflowError、OutOfMemoryError異常

  • 程式計數器
  • ①執行緒執行Java方法,計數器記錄正在執行的虛擬機器位元組碼指令地址;執行native方法,計數器為空(Undefined)
    ②此區域不會出現OutOfMemoryError異常

    OOM

  • 堆溢位
  • 不斷建立物件

    
        /**
         * VM Args:-Xmx10m
         */
        public static void main(String[] args) {
            List list = new ArrayList();
            while (true){
                list.add(new Object());
            }
        } 
    複製程式碼

    結果:
    java.lang.OutOfMemoryError: Java heap space

  • 棧溢位
  • StackOverflowError異常:

    遞迴呼叫

    
        private void test(){
            test();
        }
        public static void main(String[] args) {
            new StackTest().test();
        }
    複製程式碼

    結果:
    java.lang.StackOverflowError

    OutOfMemoryError異常:

    
    /**
     * VM Args:-Xss2M
     * @author zzm
     */
    public class JavaVMStackOOM {
    
        private void dontStop(){
            while (true){
            }
        }
    
        public void stackLeakByThread(){
            while(true){
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        dontStop();
                    }
                });
                thread.start();
            }
        }
    
        public static void main(String[] args) {
            JavaVMStackOOM oom = new JavaVMStackOOM();
            oom.stackLeakByThread();
        }
    
    }
    複製程式碼

    本人執行了一下果然像書(深入理解Java虛擬機器)中所說電腦重啟了,Windows系統的同學謹慎執行

  • 方法區溢位
  • 
    /**
     * VM Args:-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M (jdk8)
     * -XX:PermSize=10M -XX:MaxPermSize=10M(jdk7)
     * @author zzm
     */
    public class Test {
    
        static class OOMOjbect{}
    
        public static void main(String[] args) {
            while(true){
                Enhancer eh = new Enhancer();
                eh.setSuperclass(OOMOjbect.class);
                eh.setUseCache(false);
                eh.setCallback(new MethodInterceptor(){
                    @Override
                    public Object intercept(Object arg0, Method arg1,
                                            Object[] arg2, MethodProxy arg3) throws Throwable {
                        return arg3.invokeSuper(arg0, arg2);
                    }
    
                });
                eh.create();
            }
        }
    }
    複製程式碼

    本人用的是jdk8,輸出結果:
    java.lang.OutOfMemoryError: Metaspace

    物件的記憶體佈局

    物件在記憶體中佈局可以分成三塊區域:物件頭、例項資料和對齊填充

    物件頭

    物件頭包括兩部分資訊:

  • 執行時資料:
  • 執行時資料包括雜湊碼(HashCode)、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向鎖ID和偏向時間戳等,這部分資料在32位和64位虛擬機器中的長度分別為32bit和64bit,官方稱為"Mark Word"。Mark Word被設計成非固定的資料結構,以實現在有限空間內儲存儘可能多的資料。Mark Word的儲存內容如下:
    儲存內容 標誌位 狀態
    物件雜湊碼、物件分代年齡 01 未鎖定
    指向鎖記錄的指標 00 輕量級鎖定
    指向重量級鎖的指標 10 膨脹(重量級鎖定)
    空,不需要記錄資訊 11 GC標記
    偏向執行緒ID、偏向時間戳、物件分代年齡 01 可偏向

  • 型別指標:
  • 物件指向它的類後設資料的指標,虛擬機器通過這個指標來確定這個物件是哪個類的例項

    例項資料

    例項資料就是在程式程式碼中所定義的各種型別的欄位,包括從父類繼承的,這部分的儲存順序會受到虛擬機器分配策略引數和欄位在原始碼中定義順序的影響

    對齊填充

    並不是必然存在的,沒有特別的含義。由於HotSpot的自動記憶體管理要求物件的起始地址必須是8位元組的整數倍,即物件的大小必須是8位元組的整數倍,物件頭的資料正好是8的整數倍,所以當例項資料不夠8位元組整數倍時,需要通過對齊填充進行補全。

    物件訪問

    控制程式碼

    使用控制程式碼訪問的話,Java堆中將會劃分出一塊記憶體來作為控制程式碼池,reference中儲存的就是物件的控制程式碼地址,而控制程式碼中包含了物件例項資料與型別資料各自的具體地址資訊:

    JVM學習筆記——初識JVM

    直接指標

    使用直接指標訪問的話,Java堆物件的佈局中就必須考慮如何放置訪問型別資料的相關資訊,reference中儲存的直接就是物件地址

    JVM學習筆記——初識JVM

    這兩種物件訪問方式各有優勢:

  • 使用控制程式碼來訪問的最大好處就是reference中儲存的是穩定控制程式碼地址,在物件被移動(垃圾收集時移動物件是非常普遍的行為)時只會改變控制程式碼中的例項資料指標,而reference本身不需要被修改
  • 使用直接指標來訪問最大的好處就是速度更快,它節省了一次指標定位的時間開銷,由於物件訪問的在Java中非常頻繁,因此這類開銷積小成多也是一項非常可觀的執行成本
  • oracle JDK官方預設虛擬機器HotSpot採用第二中方式進行物件訪問

    感謝

    《極客時間——楊曉峰Java核心技術36講第一講》
    《深入理解Java虛擬機器》

    相關文章