JVM學習筆記——初識JVM

簡述

"Write Once,Run Anywhere"(一次編譯,處處運行)是sun宣傳Java語言所提出的口號。Java語言跨平臺的特性與Java虛擬機的存在密不可分。Java源代碼經過編譯生成.class文件字節碼後再被JVM解釋轉化爲目標機器代碼,處處運行的關鍵與前提就是JVM。因此並非Java語言自己能夠能夠跨平臺,而是在不一樣的平臺都有可讓Java語言運行的環境而已。在能夠運行Java虛擬機的地方都內含一個JVM操做系統,從而使JAVA提供了各類不一樣平臺上的虛擬機制,因而可知導出運行的關鍵就是JVM。java

JVM內存區域

JVM結構數據結構

線程共享區域

  • ①Java堆是Java虛擬機管理的內存中最大的一塊。
    ②堆中存放對象實例,幾乎全部的對象實例都在這裏分配內存,因此是GC主要區域。
    ③不須要連續的內存
    ④若堆中沒有內存完成實例分配,則會拋出OutOfMemoryError異常

  • 方法區
  • ①存儲已被虛擬機加載的類信息(類名、訪問修飾符、字段描述、方法描述等)、常量、靜態變量、即時編譯器編譯後的代碼等數據
    ②運行時常量池是方法區的一部分
    ③這區域的內存回收目標主要是針對常量池的回收和對類型的卸載
    ④不須要連續的內存,方法區gc性價比較低,gc頻率遠沒有堆中高
    ⑤若方法區沒法知足內存分配需求時,則會拋出OutOfMemoryError異常

    線程隔離區域(隨線程而生,隨線程而滅)

  • 虛擬機棧
  • ①生命週期與線程相同,管理Java方法執行的內存模型
    ②方法執行建立棧幀(棧幀所需內存在類結構肯定下來時就被已知,不考慮JIT優化)用於存儲局部變量表、操做數棧、動態連接、方法出口等信息
    ③棧的大小決定了方法調用的深度(遞歸多少層次,或嵌套調用多少層其餘方法)
    ④若線程請求的棧深度大於虛擬機所容許的深度,則拋出StackOverflowError異常
    ⑤若棧中無憂內存可分配,則拋出OutOfMemoryError異常

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

  • 程序計數器
  • ①線程執行Java方法,計數器記錄正在執行的虛擬機字節碼指令地址;執行native方法,計數器爲空(Undefined)
    ②此區域不會出現OutOfMemoryError異常

    OOM

  • 堆溢出
  • 不斷建立對象oracle

    /**
         * 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

    ide

  • 棧溢出
  • 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系統的同窗謹慎運行spa

  • 方法區溢出
  • /**
     * 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中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址信息:

    直接指針

    使用直接指針訪問的話,Java堆對象的佈局中就必須考慮如何放置訪問類型數據的相關信息,reference中存儲的直接就是對象地址

    這兩種對象訪問方式各有優點:

  • 使用句柄來訪問的最大好處就是reference中存儲的是穩定句柄地址,在對象被移動(垃圾收集時移動對象是很是廣泛的行爲)時只會改變句柄中的實例數據指針,而reference自己不須要被修改
  • 使用直接指針來訪問最大的好處就是速度更快,它節省了一次指針定位的時間開銷,因爲對象訪問的在Java中很是頻繁,所以這類開銷積小成多也是一項很是可觀的執行成本
  • oracle JDK官方默認虛擬機HotSpot採用第二中方式進行對象訪問

    感謝

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

    相關文章
    相關標籤/搜索