<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          個(gè)人筆記,深入理解 JVM,很全!

          共 4160字,需瀏覽 9分鐘

           ·

          2021-11-05 10:47

          點(diǎn)擊上方藍(lán)色字體,選擇“設(shè)為星標(biāo)”


          回復(fù)”學(xué)習(xí)資料“獲取學(xué)習(xí)寶典


          01、前言

          刷豆瓣看到《深入理解 JVM》出第三版了,遂買之更新 JVM 知識,本文為筆記,僅供個(gè)人 Review

          02、Java 內(nèi)存區(qū)域與內(nèi)存溢出

          03、運(yùn)行時(shí)數(shù)據(jù)區(qū)域

          參考:JVM 規(guī)范,Memories of a Java Runtime

          :JVM 啟動時(shí)按-Xmx, -Xms大小創(chuàng)建的內(nèi)存區(qū)域,用于分配對象、數(shù)組所需內(nèi)存,由 GC 管理和回收

          方法區(qū):存儲被 JVM 加載的類信息(字段、成員方法的字節(jié)碼指令等)、運(yùn)行時(shí)常量池(字面量、符號引用等)、JIT 編譯后的 Code Cache 等信息;JDK8 前 Hotspot 將方法區(qū)存儲于永久代堆內(nèi)存,之后參考 JRockit 廢棄了永久代,存儲于本地內(nèi)存的 Metaspace 區(qū)

          直接內(nèi)存:JDK1.4 引入 NIO 使用 Native/Unsafe 庫直接分配系統(tǒng)內(nèi)存,使用 Buffer,Channel 與其交互,避免在系統(tǒng)內(nèi)存與 JVM 堆內(nèi)存之間拷貝的開銷

          線程私有內(nèi)存

          • 程序計(jì)數(shù)器:記錄當(dāng)前線程待執(zhí)行的下一條指令位置,上下文切換后恢復(fù)執(zhí)行,由字節(jié)碼解釋器負(fù)責(zé)更新
          • JVM 棧
          • 描述 Java 方法執(zhí)行的內(nèi)存模型:執(zhí)行新方法時(shí)創(chuàng)建棧幀,存儲局部變量表、操作數(shù)棧等信息
          • 存儲單位:變量槽 slot,long, double占 2 個(gè) slot,其他基本數(shù)據(jù)類型、引用類型占 1 個(gè),故表的總長度在編譯期可知
          • 本地方法棧:執(zhí)行本地 C/C++ 方法

          04、JVM 對象

          1. 創(chuàng)建對象

          分配堆內(nèi)存:類加載完畢后,其對象所需內(nèi)存大小是確定的;堆內(nèi)存由多線程共享,若并發(fā)創(chuàng)建對象都通過 CAS 樂觀鎖爭奪內(nèi)存,則效率低。故線程創(chuàng)建時(shí)在堆內(nèi)存為其分配私有的分配緩沖區(qū)(TLAB:Thread Local Allocation Buffer)

          • 內(nèi)存模型
          • 分配流程

          注:當(dāng) TLAB 剩余空間不足以分配新對象,但又小于最大浪費(fèi)空間閾值時(shí),才會加鎖創(chuàng)建新的 TLAB

          零值初始化對象的堆內(nèi)存、設(shè)置對象頭信息、執(zhí)行構(gòu)造函數(shù)?()V

          2. 對象的內(nèi)存布局

          對象頭

          • Mark Word:記錄對象的運(yùn)行時(shí)信息,如 hashCode,GC 分代年齡,尾部 2 bit 用于標(biāo)記鎖狀態(tài)
          • Class Pointer:指向所屬的類信息

          • 數(shù)組長度(可選,對象為數(shù)組):4 字節(jié)存儲其長度

          對象數(shù)據(jù):各種字段的值,按寬度分類緊鄰存儲

          對齊填充:內(nèi)存對齊為 1 個(gè)字長整數(shù)倍,減少 CPU 總線周期

          驗(yàn)證:openjdk/jol 檢查對象內(nèi)存布局

          public?class?User?{
          private?int?age?=?-1;
          private?String?name?=?"unknown";
          }

          //?java?-jar?~/Downloads/jol-cli-latest.jar?internals?-cp?.?com.jol.User
          OFF??SZ???????????????TYPE?DESCRIPTION???????????????VALUE
          0???8????????????????????(object?header:?mark)?????0x0000000000000001?(non-biasable;?age:?0)
          8???4????????????????????(object?header:?class)????0xf8021e85?//?User.class?引用地址
          ?12???4????????????????int?User.age??????????????????-1?????????//?基本類型則直接存儲值
          ?16???4???java.lang.String?User.name?????????????????(object)???//?引用類型,指向運(yùn)行時(shí)常量池中的?String?對象
          ?20???4????????????????????(object?alignment?gap)???????????????//?有?4?字節(jié)的內(nèi)存填充
          Instance?size:?24?bytes

          05、內(nèi)存溢出

          堆內(nèi)存-Xms指定堆初始大小,當(dāng)大量無法被回收的對象所占內(nèi)存超出-Xmx上限時(shí),將發(fā)生內(nèi)存溢出?OutOfMemoryError

          • 排查:通過 Eclipse MAT 分析?-XX:+HeapDumpOnOutOfMemory生成的 *.hprof 堆轉(zhuǎn)儲文件,定位無法被回收的大對象,找出其 GC Root 引用路徑

          • 解決:若為內(nèi)存泄露,則修改代碼用null顯式賦值、虛引用等方式及時(shí)回收大對象;若為內(nèi)存溢出,大對象都是必須存活的,則調(diào)大-Xmx、減少大對象的生命周期、檢查數(shù)據(jù)結(jié)構(gòu)使用是否合理等

          //?-Xms20m?-Xmx20m?-XX:+HeapDumpOnOutOfMemoryError
          public?class?HeapOOM?{
          ?static?class?OOMObject?{}
          ?public?static?void?main(String[]?args)?{
          ??List?vs?=?new?ArrayList<>();
          ??while?(true)
          ?????vs.add(new?OOMObject());
          ?}
          }

          分析 GC Root 發(fā)現(xiàn)com.ch02.HeapOOM對象間接引用了大量的OOMObject對象,共占用 15.4MB 堆內(nèi)存,無法回收最終導(dǎo)致 OOM

          棧內(nèi)存-Xss指定棧大小,當(dāng)棧深度超閾值(比如未觸發(fā)終止條件的遞歸調(diào)用)、本地方法變量表過大等,都可能導(dǎo)致內(nèi)存溢出?StackOverflowError

          方法區(qū)-XX:MetaspaceSize指定元空間初始大小,-XX:MaxMetaspaceSize指定最大大小,默認(rèn) -1 無限制,若在運(yùn)行時(shí)動態(tài)生成大量的類,則可能觸發(fā) OOM

          運(yùn)行時(shí)常量池strObj.intern()動態(tài)地將首次出現(xiàn)的字符串對象放入字符串常量池并返回,JDK7 前會拷貝到永久代,之后則直接引用堆對象

          String?s1?=?"java";?//?類加載時(shí),從字節(jié)碼常量池中拷貝符號到了運(yùn)行時(shí)常量池,在解析階段初始化的字符串對象
          String?s2?=?"j";
          String?s3?=?s2?+?"ava";?//?堆上動態(tài)分配的字符串對象
          println(s3?==?s1);??????????//?false
          println(s3.intern()?==?s1);?//?true?//?已在字符串常量池中存在

          直接內(nèi)存-XX:MaxDirectMemorySize指定大小,默認(rèn)與-Xmx一樣大,不被 GC 管理,申請內(nèi)存超閾值時(shí) OOM


          06、垃圾回收與內(nèi)存分配

          GC 可分解為 3 個(gè)子問題:which(哪些內(nèi)存可被回收)、when(什么時(shí)候回收)、how(如何回收)

          07、GC 條件

          1. 引用計(jì)數(shù)算法(reference counting)

          原理:每個(gè)對象都維護(hù)一個(gè)引用計(jì)數(shù)器rc,當(dāng)通過賦值、傳參等方式引用它時(shí)rc++,當(dāng)引用變量修改指向、離開函數(shù)作用域等方式解除引用時(shí)rc--,遞減到 0 時(shí)說明對象無法再被使用,可回收。偽代碼:

          assign(var,?obj):
          incr_ref(obj)?#?self?=?self?#?先增再減,避免引用自身導(dǎo)致內(nèi)存提前釋放
          decr_ref(var)
          var?=?obj
          ?
          incr(obj):
          obj.rc++

          decr(obj):
          obj.rc--
          if?obj.rc?==?0:
          ??remove_ref(obj)?#?斷開?obj?與其他對象的引用關(guān)系
          ??gc(obj)?????????#?回收?obj?內(nèi)存

          優(yōu)點(diǎn):思路簡單,對象無用即回收,延遲低,適合內(nèi)存少的場景

          缺點(diǎn):此算法中對象是孤立的,無法在全局視角檢查對象的真實(shí)有效性,循環(huán)引用的雙方對象需引入外部機(jī)制來檢測和回收,如下圖紅色圈(圖源:what-is-garbage-collection)

          2. 可達(dá)性分析算法(reachability analysis)

          原理:從肯定不會被回收的對象(GC Roots)出發(fā),向外搜索全局對象圖,不可達(dá)的對象即無法再被使用,可回收;常見可作為 GC Root 的對象有:

          • 執(zhí)行上下文:JVM 棧中參數(shù)、局部變量、臨時(shí)變量等引用的堆對象
          • 全局引用:方法區(qū)中類的靜態(tài)引用、常量引用(如 StringTable 中的字符串對象)所指向的對象

          優(yōu)點(diǎn):無需對象維護(hù) GC 元信息,開銷小;單次掃描即可批量識別、回收對象,吞吐高

          缺點(diǎn):多線程環(huán)境下對象間的引用關(guān)系隨時(shí)在變化,為保證 GC Root 標(biāo)記的準(zhǔn)確性,需在不變化的 snapshot 中進(jìn)行,會產(chǎn)生 Stop The World(以下簡稱 STW) 卡頓現(xiàn)象

          3. 四種引用類型

          引用類型回收時(shí)機(jī)
          強(qiáng)引用-只要與 GC Root 存在引用鏈,則不被回收
          軟引用SoftReference只被軟引用所引用的對象,當(dāng) GC 后內(nèi)存依然不足,才被回收
          弱引用WeakReference只被弱引用所引用的對象,無論內(nèi)存是否足夠,都將被回收
          虛引用PhantomReference被引用的對象無感知,進(jìn)行正常 GC,僅在回收時(shí)通知虛引用(回調(diào))

          示例:限制堆內(nèi)存 50MB,其中新生代 30MB,老年代 20MB;依次分配 5 次 10MB 的byte[]對象,僅使用軟引用來引用,觀察 GC 過程

          public?static?void?main(String[]?args)?{
          //?softRefList?-->?SoftReference?-->?10MB?byte[]?
          List>?softRefList?=?new?ArrayList<>();
          ReferenceQueue?softRefQueue?=?new?ReferenceQueue<>();?//?無效引用隊(duì)列
          for?(int?i?=?0;?i???SoftReference?softRef?=?new?SoftReference<>(new?byte[10*1024*1024],?softRefQueue);
          ??softRefList.add(softRef);

          ??for?(SoftReference?ref?:?softRefList)?//?dump?所有軟引用指向的對象,檢查是否已被回收
          ??????System.out.print(ref.get()?==?null???"gced?"?:?"ok?");
          ??System.out.println();
          }
          Reference?ref?=?softRefQueue.poll();
          while?(ref?!=?null)?{
          ??softRefList.remove(ref);?//?解除對軟引用對象本身的引用
          ??ref?=?softRefQueue.poll();
          }
          System.out.println("effective?soft?ref:?"?+?softRefList.size());?//?2
          }

          //?java?-verbose:gc?-XX:NewSize=30m?-Xms50m?-Xmx50m?-XX:+PrintGCDetails?com.ch02.DemoRef
          ok?
          ok?ok?
          //?分配第三個(gè)?[]byte?時(shí),Eden?GC?無效,觸發(fā)?Full?GC?將一個(gè)?[]byte?晉升到老年區(qū)
          //?此時(shí)三個(gè)?byte[]?都只被軟引用所引用,被標(biāo)記為待二次回收(若為弱引用,此時(shí)?Eden?已被回收)
          [GC?(Allocation?Failure)?--[PSYoungGen:?21893K->21893K(27136K)]?21893K->32141K(47616K),?0.0046324?secs]
          [Full?GC?(Ergonomics)?[PSYoungGen:?21893K->10527K(27136K)]?[ParOldGen:?10248K->10240K(20480K)]?32141K->20767K(47616K),?[Metaspace:?2784K->2784K(1056768K)],?0.004?secs]
          ok?ok?ok
          //?再次?GC,前三個(gè)?byte[]?全部被回收
          [GC?(Allocation?Failure)?--[PSYoungGen:?20767K->20767K(27136K)]?31007K->31007K(47616K),?0.0007963?secs]
          [Full?GC?(Ergonomics)?[PSYoungGen:?20767K->20759K(27136K)]?[ParOldGen:?10240K->10240K(20480K)]?31007K->30999K(47616K),?[Metaspace:?2784K->2784K(1056768K)],?0.003?secs]
          [GC?(Allocation?Failure)?--[PSYoungGen:?20759K->20759K(27136K)]?30999K->30999K(47616K),?0.0007111?secs]
          [Full?GC?(Allocation?Failure)?[PSYoungGen:?20759K->0K(27136K)]?[ParOldGen:?10240K->267K(20480K)]?30999K->267K(47616K),?[Metaspace:?2784K->2784K(1056768K)],?0.003?secs]
          gced?gced?gced?ok
          gced?gced?gced?ok?ok

          4. finalize

          原理:若對象不可達(dá),被標(biāo)記為可回收后,會進(jìn)行finalize()是否被重寫、是否已執(zhí)行過等條件篩選,若通過則對象會被放入 F-Queue 隊(duì)列,等待低優(yōu)先級的后臺 Finalizer 線程觸發(fā)其finallize()?的執(zhí)行(不保證執(zhí)行結(jié)束),對象可在finalize中建立與 GC Root 對象圖上任一節(jié)點(diǎn)的引用關(guān)系,來逃脫 GC

          使用:finalize 機(jī)制與 C++ 中的析構(gòu)函數(shù)并不等價(jià),其執(zhí)行結(jié)果并不確定,不推薦使用,可用try-finally替代


          08、GC 算法

          分代收集理論

          兩個(gè)分代假說:符合大多數(shù)程序運(yùn)行的實(shí)際情況

          對應(yīng)地,JVM 堆被劃分為 2 個(gè)不同區(qū)域,將對象按年齡分類,兼顧了 GC 耗時(shí)與內(nèi)存利用率

          跨代引用

          • 問題:老年代會引用新生代,新生代 GC 時(shí)需遍歷老年代中大量的存活對象,分析可達(dá)性,時(shí)間復(fù)雜度高
          • 背景:相互引用的對象傾向于同時(shí)存亡,比如跨代引用關(guān)系中的新生代必然會逐步晉升,最終消除跨代關(guān)系
          • 假說:跨代引用相比同代引用只占極少數(shù),無需全量掃描老年代
          • 實(shí)現(xiàn):新生代維護(hù)全局?jǐn)?shù)據(jù)結(jié)構(gòu):記憶集(Remembered Set),將老年代分為多個(gè)子塊,標(biāo)記存在跨代引用的子塊,等待后續(xù)掃描;代價(jià):為保證記憶集的正確性,需在跨代引用建立或斷開時(shí)保持同步

          09、標(biāo)記清除:Mark-Sweep

          • 原理:標(biāo)記不可達(dá)對象,統(tǒng)一清理回收,反之亦可
          • 缺點(diǎn):執(zhí)行效率不穩(wěn)定,回收耗時(shí)取決于活躍對象的數(shù)量;內(nèi)存碎片多,會出現(xiàn)內(nèi)存充足但無法分配過大的連續(xù)內(nèi)存(數(shù)組)

          10、標(biāo)記復(fù)制:Mark-Copy

          • 理論:將堆內(nèi)存切為兩等份 A, B,每次僅使用 A,用完后標(biāo)記存活對象復(fù)制到 B,清空 A 后執(zhí)行 swap
          • 優(yōu)點(diǎn):直接針對半?yún)^(qū)回收,無內(nèi)存碎片問題;分配內(nèi)存只需移動堆頂指針,高效順序分配
          • 缺點(diǎn):當(dāng) A 區(qū)有大量存活對象時(shí),復(fù)制開銷大;B 區(qū)長時(shí)間閑置,內(nèi)存浪費(fèi)嚴(yán)重
          • 實(shí)踐:對于存活對象少的新生代,無需按 1:1 分配,而是按 8:1:1 的內(nèi)存布局,其中 Eden 和 From 區(qū)同時(shí)使用,只有 To 區(qū)會被閑置(擔(dān)保機(jī)制:若 To 區(qū)不夠容納 Minor GC 后的存活對象,則晉升到老年區(qū))

          11、標(biāo)記整理:Mark-Compact

          • 原理:標(biāo)記存活對象后統(tǒng)一移動到內(nèi)存空間一側(cè),再回收邊界之外的內(nèi)存
          • 優(yōu)點(diǎn):內(nèi)存模型簡單,無內(nèi)存碎片,降低內(nèi)存分配和訪問的時(shí)間成本,能提高吞吐
          • 缺點(diǎn):對象移動需 STW 同步更新引用關(guān)系,會增加延遲

          12、HotSpot GC 算法細(xì)節(jié)

          13、發(fā)起 GC:安全點(diǎn)與安全區(qū)域

          示意圖

          14、加速 GC:CardTable

          問題:非收集區(qū)域(老年代)會存在到收集區(qū)域(新生代)的跨代引用,如何避免對前者的全量掃描?

          卡表:記憶集的字節(jié)數(shù)組實(shí)現(xiàn);將老年代內(nèi)存劃分為 Card Page(512KB)大小的子內(nèi)存塊,若新建跨代引用,則將對應(yīng)的 Card 標(biāo)記為 dirty,GC 時(shí)只需掃描老年代中被標(biāo)記為 dirty 的子內(nèi)存塊

          寫屏障:有別于volatile禁用指令重排的內(nèi)存屏障,GC 中的寫屏障是在對象引用更新時(shí)執(zhí)行額外 hook 動作的機(jī)制。簡單實(shí)現(xiàn):

          void?oop_field_store(oop*?field,?oop?new_val)?{?//?oop:?ordinary?object?pointer
          // pre_write_barrier(field, new_val);?//?寫前屏障:更新前先執(zhí)行,使用 oop 舊狀態(tài)
          *field?=?new_val;
          post_write_barrier(field, new_val);?//?寫后屏障:更新完才執(zhí)行
          }

          使用寫屏障保證 CardTable 的實(shí)時(shí)更新(圖源:The JVM Write Barrier - Card Marking)

          15、正確 GC:并發(fā)可達(dá)性分析

          參考演講:Shenandoah: The Garbage Collector That Could by Aleksey Shipilev

          **問題:**GC Roots 的對象源固定,故枚舉時(shí) STW 時(shí)間短暫且可控。但后續(xù)可達(dá)性分析的時(shí)間復(fù)雜度與堆中對象數(shù)量成正相關(guān),即堆中對象越多,對象圖越復(fù)雜,堆變大后 STW 時(shí)間不可接受

          **解決:**并發(fā)標(biāo)記。引出新問題:用戶線程動態(tài)建立、解除引用,標(biāo)記過程中圖結(jié)構(gòu)發(fā)生變化,結(jié)果不可靠;證明:用三色法描述對象狀態(tài)

          • 白色:未被回收器訪問過的對象;分析開始都是白色,分析結(jié)束還是白色則不可達(dá)
          • 灰色:被回收器訪問過,但其上至少還有 1 個(gè)引用未被掃描(中間態(tài))
          • 黑色:被回收器訪問過,其上引用全部都已被掃描,存在引用鏈,為存活對象;若其他對象引用了黑色對象,則不必再掃描,肯定也存活;黑色不可能直接引用白色

          STW 無并發(fā)的正確標(biāo)記:頂部 3 個(gè)對象將被回收

          用戶線程并發(fā)修改引用,會導(dǎo)致標(biāo)記結(jié)果無效,分 2 種情況:

          • 少回收,對象標(biāo)記為存活,但用戶解除了引用:產(chǎn)生浮動垃圾,可接受,等待下次 GC
          • 誤回收,對象標(biāo)記為可回收,但用戶新建了引用:實(shí)際存活對象被回收,內(nèi)存錯誤

          論文《Uniprocessor Garbage Collection Techniques - Paul R. Wilson》§3.2 證明了「實(shí)際存活的對象被標(biāo)記為可回收」必須同時(shí)滿足兩個(gè)條件(有時(shí)間序)

          • 插入一條或多條從黑色到白色的新引用
          • 刪除所有灰色到該白色的直接、間接引用

          為正確實(shí)現(xiàn)標(biāo)記,打破其中一個(gè)條件即可(類比打破死鎖四個(gè)條件之一的思想),分別對應(yīng)兩種方案:

          • 增量更新 Increment Update:記錄黑到白的引用關(guān)系,并發(fā)標(biāo)記結(jié)束后,以黑為根,重新掃描;A 直接存活
          • 原始快照 SATB(Snapshot At The Begining):記錄灰到白的解引用關(guān)系,并發(fā)標(biāo)記結(jié)束后,以灰為根,重新掃描;B 為灰色,最后變?yōu)楹谏婊睢P枳⒁猓魶]有步驟 3,則 B,C 變?yōu)楦永?/section>

          16、經(jīng)典垃圾回收器

          搭配使用示意圖:

          17、Serial, SerialOld

          原理:內(nèi)存不足觸發(fā) GC 后會暫停所有用戶線程,單線程地在新生代中標(biāo)記復(fù)制,在老年代中標(biāo)記整理,收集完畢后恢復(fù)用戶線程

          優(yōu)點(diǎn):全程 STW 簡單高效

          缺點(diǎn):STW 時(shí)長與堆對象數(shù)量成正相關(guān),且 GC 線程只能用到 1 core 無法加速

          場景:單核 CPU 且可用內(nèi)存少(如百兆級),JDK1.3 之前的唯一選擇

          18、ParNew

          原理:多線程并行版的 Serial 實(shí)現(xiàn),能有效減少 STW 時(shí)長;線程數(shù)默認(rèn)與核數(shù)相同,可配置

          場景:JDK7 之前搭配老年代的 CMS 回收器使用

          19、Parallel, Parallel Old

          垃圾回收有兩個(gè)通常不可兼得的目標(biāo)

          • 低延遲:STW 時(shí)長短,響應(yīng)快;允許高頻、短暫 GC,比如調(diào)小新生代空間,加快收集延遲(吞吐下降)
          • 高吞吐量:用戶線程耗時(shí) /(用戶線程耗時(shí) + GC 線程耗時(shí))高,GC 總時(shí)間低;允許低頻、單次長時(shí)間 GC,(延遲增加)

          原理:與 ParNew 類似都是并行回收,主要增加了 3 個(gè)選項(xiàng)(傾向于提高吞吐量)

          • -XX:MaxGCPauseTime:控制最大延遲
          • -XX:GCTimeRatio:控制吞吐(默認(rèn) 99%)
          • -XX:+UseAdaptiveSizePolicy?:啟用自適應(yīng)策略,自動調(diào)整 Eden 與 2 個(gè) Survivor 區(qū)的內(nèi)存占比-XX:SurvivorRatio,老年代晉升閾值?-XX:PretenureSizeThreshold

          20、CMS

          CMS:Concurrent Mark Sweep,即并發(fā)標(biāo)記清除,主要有 4 個(gè)階段

          • 初始標(biāo)記(initial mark):STW 快速收集 GC Roots
          • 并發(fā)標(biāo)記(concurrent mark):從 GC Roots 出發(fā)檢測引用鏈,標(biāo)記可回收對象;與用戶線程并發(fā)執(zhí)行,通過增量更新來避免誤回收
          • 重新標(biāo)記(remark):STW 重新分析被增量更新所收集的 GC Roots
          • 并發(fā)清除(concurrent sweep):并發(fā)清除可回收對象

          優(yōu)點(diǎn):兩次 STW 時(shí)間相比并發(fā)標(biāo)記耗時(shí)要短得多,相比前三種收集器,延遲大幅降低

          缺點(diǎn)

          • CPU 敏感:若核數(shù)較少(< 4core),并發(fā)標(biāo)記將占用大量 CPU 時(shí)間,會導(dǎo)致吞吐突降
          • 無法處理浮動垃圾:-XX:CMSInitiatingOccupancyFration(默認(rèn) 92%)指定觸發(fā) CMS GC 的閾值;在并發(fā)標(biāo)記、并發(fā)清理的同時(shí),用戶線程會產(chǎn)生浮動垃圾(引用可回收對象、產(chǎn)生新對象),若浮動垃圾占比超過-XX:CMSInitiatingOccupancyFration;若 GC 的同時(shí)產(chǎn)生過多的浮動垃圾,導(dǎo)致老年代內(nèi)存不足,會出現(xiàn) CMS 并發(fā)失敗,退化為 Serial Old 執(zhí)行 Full GC,會導(dǎo)致延遲突增
          • 無法避免內(nèi)存碎片:-XX:CMSFullGCsBeforeCompaction(默認(rèn) 0)指定每次在 Full GC 前,先整理老年代的內(nèi)存碎片

          21、G1

          特點(diǎn):基于 region 內(nèi)存布局實(shí)現(xiàn)局部回收;GC 延遲目標(biāo)可配置;無內(nèi)存碎片問題


          G1之前回收器
          堆內(nèi)存劃分方式多個(gè)等大的 region, 各 region 分代角色并不固定,按需在 Eden, Survivor, Old 間切換固定大小、固定數(shù)量的分代區(qū)域
          回收目標(biāo)回收價(jià)值高的 region 動態(tài)組成的回收集合新生代、整個(gè)堆內(nèi)存

          跨代引用:各 region 除了用卡表標(biāo)記各卡頁是否為 dirty 之外,還用哈希表記錄了各卡頁正在被哪些 region 引用,通過這種“雙向指針”機(jī)制,能直接找到 Old 區(qū),避免了全量掃描(G1 自身內(nèi)存開銷大頭)

          G1 GC 有 3 個(gè)階段(參考其 GC 日志)

          • 新生代 GC:Eden 區(qū)占比超閾值觸發(fā);標(biāo)記存活對象并復(fù)制到 Survivor 區(qū),其內(nèi)可能有對象會晉升到 Old 區(qū)
          • 老年代 GC:Old 區(qū)占比達(dá)到閾值后觸發(fā),執(zhí)行標(biāo)記整理

          • 初始標(biāo)記:枚舉 GC Roots,已在新生代 GC 時(shí)順帶完成

          • 并發(fā)標(biāo)記:并發(fā)執(zhí)行可達(dá)性分析,使用 SATB 記錄引用變更

          • 重新標(biāo)記:SATB 分析,避免誤回收

          • 篩選回收:將 region 按回收價(jià)值和時(shí)間成本篩選組成回收集,STW 將存活對象拷貝到空 regions 后清理舊 regions,完成回收

          • 混合 GC

          參數(shù)控制(文檔:HotSpot GC Tuning Guide)

          參數(shù)及默認(rèn)值描述
          ‐XX:+UseG1GCJDK9 之前手動啟用 G1
          -XX:MaxGCPauseMillis=200預(yù)期的最大 GC 停頓時(shí)間;不宜過小,避免每次回收內(nèi)存少而導(dǎo)致頻繁 GC
          -XX:ParallelGCThreads=NSTW 并行線程數(shù),若可用核數(shù) M < 8 則 N=1,否則默認(rèn) N=M*5/8
          -XX:ConcGCThreads=N并發(fā)階段并發(fā)線程數(shù),默認(rèn)是 ParallelGCThreads 的 1/4
          -XX:InitiatingHeapOccupancyPercent=45老年代 region 占比超過 45% 則觸發(fā)老年代 GC
          -XX:G1HeapRegionSize=N單個(gè) region 大小,1~32MB
          -XX:G1NewSizePercent=5, -XX:G1MaxNewSizePercent=60新生代 region 最小占整堆的 5%,最大 60%,超出則觸發(fā)新生代 GC
          -XX:G1HeapWastePercent=5允許浪費(fèi)的堆內(nèi)存占比,可回收內(nèi)存低于 5% 則不進(jìn)行混合回收
          -XX:G1MixedGCLiveThresholdPercent=85老年代存活對象占比超 85%,回收價(jià)值低,暫不回收
          -XX:G1MixedGCCountTarget=8單次收集中混合回收次數(shù)

          22、內(nèi)存分配策略

          使用 Serial 收集器?-XX:+UseG1GC?演示

          1. 對象優(yōu)先分配在 Eden 區(qū)

          新對象在 Eden 區(qū)分配,空間不足則觸發(fā) Minor GC,存活對象拷貝到 To Survivor,若還是內(nèi)存不足則通過分配擔(dān)保機(jī)制轉(zhuǎn)移到老年區(qū),依舊不足才 OOM

          byte[]?buf1?=?new?byte[6?*?MB];
          byte[]?buf2?=?new?byte[6?*?MB];?//?10MB?的?eden?區(qū)剩余?4MB,空間不足,觸發(fā)?minor?GC

          //?java?-verbose:gc?-Xms20m?-Xmx20m?-Xmn10m?-XX:+PrintGCDetails?-XX:+UseSerialGC?com.ch03.Allocation
          //?minor?gc?后新生代內(nèi)存從?6M?降到?0.2M,存活對象移到了老年區(qū),總的堆內(nèi)存用量依舊是?6MB
          [GC?(Allocation?Failure)?[DefNew:?6823K->286K(9216K),?0.002?secs]?6823K->6430K(19456K),?0.002?secs]?[Times:?user=0.00?sys=0.00,?real=0.00?secs]?
          Heap
          ?def?new?generation???total?9216K,?used?6513K?
          eden?space?8192K,??76%?used?//?buf2
          from?space?1024K,??28%?used
          to???space?1024K,???0%?used?
          ?tenured?generation???total?10240K,?used?6144K
          ?the?space?10240K,??60%?used?//?buf1

          2. 大對象直接進(jìn)入老年區(qū)

          對于 Serial, ParNew,可配置超過閾值?-XX:PretenureSizeThreshold?的大對象(連續(xù)內(nèi)存),直接在老年代中分配,避免觸發(fā) minor gc,導(dǎo)致 Eden 和 Survivor 產(chǎn)生大量的內(nèi)存復(fù)制操作

          byte[]?buf1?=?new?byte[4?*?MB];

          //?java?-verbose:gc?-Xms20m?-Xmx20m?-Xmn10m?-XX:+PrintGCDetails?-XX:+UseSerialGC
          //?-XX:PretenureSizeThreshold=3145728?com.ch03.Allocation?//?3145728?即?3MB
          Heap
          ?def?new?generation???total?9216K,?used?843K?
          eden?space?8192K,??10%?used?
          from?space?1024K,???0%?used?
          to???space?1024K,???0%?used?
          ?tenured?generation???total?10240K,?used?4096K?
          ?the?space?10240K,??40%?used?//?buf1

          3. 長期存活的對象進(jìn)入老年代

          對象頭中 4bit 的 age 字段存儲了對象當(dāng)前 GC 分代年齡,當(dāng)超過閾值-XX:MaxTenuringThreshold(默認(rèn) 15,也即 age 字段最大值)后,將晉升到老年代,可搭配-XX:+PrintTenuringDistribution觀察分代分布

          byte[]?buf1?=?new?byte[MB?/?16];
          byte[]?buf2?=?new?byte[4?*?MB];
          byte[]?buf3?=?new?byte[4?*?MB];?//?觸發(fā)?minor?gc
          buf3?=?null;
          buf3?=?new?byte[4?*?MB];

          //?java?-verbose:gc?-Xms20m?-Xmx20m?-Xmn10m?-XX:+PrintGCDetails?-XX:+UseSerialGC?
          //?-XX:MaxTenuringThreshold=1?-XX:+PrintTenuringDistribution?com.ch03.Allocation
          [GC?(Allocation?Failure)?[DefNew
          Desired?survivor?size?524288?bytes,?new?threshold?1?(max?1)
          -?age???1:?????359280?bytes,?????359280?total
          :?4839K->350K(9216K)]?4839K->4446K(19456K),?0.0017247?secs]?
          //?至此,buf1?熬過了第一次收集,age=1
          [GC?(Allocation?Failure)?[DefNew
          Desired?survivor?size?524288?bytes,?new?threshold?1?(max?1):?4446K->0K(9216K)]?8542K->4438K(19456K)]?
          Heap
          ?def?new?generation???total?9216K,?used?4178K?
          eden?space?8192K,??51%?used?
          from?space?1024K,???0%?used?//?buf1?在第二輪收集中被提前晉升
          to???space?1024K,???0%?used?
          ?tenured?generation???total?10240K,?used?4438K?
          ?the?space?10240K,??43%?used?

          4. 分代年齡動態(tài)判定

          -XX:MaxTenuringThreshold并非晉升的最低硬性門檻,當(dāng) Survivor 中同齡對象超 50% 后,大于等于該年齡的對象會被自動晉升,哪怕還沒到閾值

          5. 空間分配擔(dān)保

          老年代作為 To Survivor 區(qū)的擔(dān)保區(qū)域,當(dāng) Eden + From Survivor 中存活對象的總大小超出 To Survivor 時(shí),將嘗試存入老年代。JDK6 之后,只要老年代的連續(xù)空間大于新生代對象的總大小,或之前晉升的平均大小,則只會進(jìn)行 Minor GC,否則進(jìn)行 Full GC


          23、類文件結(jié)構(gòu)

          Class 文件實(shí)現(xiàn)語言無關(guān)性,JVM 實(shí)現(xiàn)平臺無關(guān)性,參考《Java 虛擬機(jī)規(guī)范》

          一個(gè) Class 文件描述了一個(gè)類或接口的明確定義,文件內(nèi)容是一組以 8 字節(jié)為單位的二進(jìn)制流,各數(shù)據(jù)項(xiàng)間沒有分隔符,超過 8 字節(jié)的數(shù)據(jù)項(xiàng)按 Big-Endian 切分后存儲。數(shù)據(jù)項(xiàng)分兩種:

          • 無符號數(shù):描述基本類型;用?u1,u2,u4,u8?分別表示?1,2,4,8?字節(jié)長度的無符號數(shù);存儲數(shù)字值、索引序號、UTF-8 編碼值等
          • 表:由無符號數(shù)、其他表嵌套構(gòu)成的復(fù)合類型;約定?_info?后綴;存儲字段類型、方法簽名等

          24、結(jié)構(gòu)定義

          25、語法

          參考文檔:The class File Format

          ClassFile?{
          u4?????????????magic;?????????//?魔數(shù)
          u2?????????????minor_version;?//?版本號
          u2?????????????major_version;
          u2?????????????constant_pool_count;?//?常量池
          cp_info????????constant_pool[constant_pool_count-1];
          u2?????????????access_flags;?????//?類訪問標(biāo)記
          u2?????????????this_class;???????//?本類全限定名
          u2?????????????super_class;??????//?單一父類
          u2?????????????interfaces_count;?//?多個(gè)接口
          u2?????????????interfaces[interfaces_count];
          u2?????????????fields_count;??//?字段表
          field_info?????fields[fields_count];
          u2?????????????methods_count;?//?方法表
          method_info????methods[methods_count];
          u2?????????????attributes_count;?//?類屬性
          attribute_info?attributes[attributes_count];
          }
          • magic:魔數(shù),簡單識別?*.class?文件,值固定為?0xCAFEBABE

          • minor_version, major_version:Class 文件的次、主版本號

          • constant_pool_count:常量池大小+1

          • constant_pool:常量池,索引從 1 開始,0 值被預(yù)留表示不引用任何常量池中的任何常量;常量分兩類

          • 字面量:如 UTF8 字符串、int、float、long、double 等數(shù)字常量

          • 符號引用:類、接口的全限定名、字段名與描述符、方法類型與描述符等 現(xiàn)有常量共計(jì) 17 種,常量間除了都使用u1 tag前綴標(biāo)識常量類型外,結(jié)構(gòu)互不相同,常見的有:

          • CONSTANT_Utf8_info:保存由 UTF8 編碼的字符串

          CONSTANT_Utf8_info?{
          ??u1?tag;???????????//?值為?1
          ??u2?length;????????//?bytes?數(shù)組長度,u2?最大值?65535,即單個(gè)字符串字面量不超過?64KB
          ??u1?bytes[length];?//?長度不定的字節(jié)數(shù)組
          }
          • CONSTANT_Class_info:表示類或接口的符號引用
          CONSTANT_Class_info?{
          ??u1?tag;????????//?值為?7
          ??u2?name_index;?//?指向全限定類名的?Utf8_info?//?常量間存在層級組合關(guān)系
          }
          • CONSTANT_Fieldref_info, CONSTANT_Methodref_info, CONSTANT_NameAndType_info:成員變量、成員方法及其類型描述符
          CONSTANT_Fieldref_info?{
          ??u1?tag;?????????????????//?值為?9
          ??u2?class_index;?????????//?所屬類
          ??u2?name_and_type_index;?//?字段的名稱、類型描述符
          }
          CONSTANT_Methodref_info?{
          ??u1?tag;?????????????????//?值為?10
          ??u2?class_index;?????????//?所屬類
          ??u2?name_and_type_index;?//?方法的名稱、簽名描述符
          }
          CONSTANT_NameAndType_info?{
          ??u1?tag;??????????????//?值為?12
          ??u2?name_index;???????//?字段或方法的名稱
          ??u2?descriptor_index;?//?類型描述符
          }

          如上只列舉了其中 5 種常量的結(jié)構(gòu),可見常量間通過組合的方式,來描述層級關(guān)系

          • access_flags:類的訪問標(biāo)記,有 16bit,每個(gè)標(biāo)記對應(yīng)一個(gè)位,比如ACC_PUBLIC對應(yīng)0x0001,表示類被 public 修飾;其他 8 個(gè)標(biāo)記參考?Opcodes.ACC_XXX

          • this_class, super_class:指向本類、唯一父類的 Class_info 符號常量

          • interface_count, interfaces:描述此類實(shí)現(xiàn)的多個(gè)接口信息

          • fields_count, fields:字段表;描述類字段、成員變量的個(gè)數(shù)及詳細(xì)信息

          field_info?{
          u2?????????????access_flags;?????//?作用域、static,final,volatile?等訪問標(biāo)記
          u2?????????????name_index;???????//?字段名
          u2?????????????descriptor_index;?//?類型描述符
          u2?????????????attributes_count;?//?字段的屬性表
          attribute_info?attributes[attributes_count];
          }

          類型描述符簡化描述了字段的數(shù)據(jù)類型、方法的參數(shù)列表及返回值,與 Java 中的類型對于關(guān)系如下:

          • 基本類型:Z:boolean, B:byte, C:char, S:short, I:int, F:float, D:double, J:long

          • void 及引用類型:V:void

          • 引用類型:L:_,類名中的 . 替換為 /,添加 ; 分隔符,如 Object 類描述為Ljava/lang/Object;

          • 數(shù)組類型:每一維用一個(gè)前置?[?表示 示例:boolean regionMatch(int, String, int, int)對應(yīng)描述符為?(ILjava/lang/String;II)Z

          • methods_count, methods:方法表;完整描述各成員方法的修飾符、參數(shù)列表、返回值等簽名信息

          method_info?{
          u2?????????????access_flags;?????//?訪問標(biāo)記
          u2?????????????name_index;???????//?方法名
          u2?????????????descriptor_index;?//?方法描述符
          u2?????????????attributes_count;?//?方法屬性表
          attribute_info?attributes[attributes_count];
          }

          字段表、方法表都可以帶多個(gè)屬性表,如常量字段表、方法字節(jié)碼指令表、方法異常表等。屬性模板:

          attribute_info?{
          u2?attribute_name_index;???//?屬性名
          u4?attribute_length;???????//?屬性數(shù)據(jù)長度
          u1?info[attribute_length];?//?其他字段,各屬性的結(jié)構(gòu)不同
          }

          屬性有 20+ 種,此處只記錄常見的三種

          • Code 屬性:存儲方法編譯后的字節(jié)碼指令
          Code_attribute?{
          ??u2?attribute_name_index;?//?屬性名,指向的?Utf8_info?值固定為?"Code"
          ??u4?attribute_length;?????//?剩下字節(jié)長度
          ??u2?max_stack;??//?操作數(shù)棧最大深度,對于此方法的棧幀中操作數(shù)棧的深度
          ??u2?max_locals;?//?以?slot?變量槽為單位的局部變量表大小,存儲隱藏參數(shù)?this,實(shí)參列表,catch?參數(shù),局部變量等
          ??u4?code_length;???????//?字節(jié)碼指令總長度
          ??u1?code[code_length];?//?JVM?指令集大小?200+,單個(gè)指令的編號用?u1?描述
          ??u2?exception_table_length;?//?異常表,描述方法內(nèi)各指令區(qū)間產(chǎn)生的異常及其?handler?地址
          ??{???u2?start_pc;???//?catch_type?類型的異常,會在?[start_pc,?end_pc)?指令范圍內(nèi)拋出
          ??????u2?end_pc;???
          ??????u2?handler_pc;?//?若拋出此異常,則?goto?到?handler_pc?處執(zhí)行
          ??????u2?catch_type;
          ??}?exception_table[exception_table_length];
          ??u2?attributes_count;?//?Code?屬性自己的屬性
          ??attribute_info?attributes[attributes_count];
          }
          • LineNumberTable 屬性:記錄 Java 源碼行號與字節(jié)碼行號的對應(yīng)關(guān)系,用于拋異常時(shí)顯示堆棧對應(yīng)的行號等信息。可作為 Code 屬性的子屬性
          LineNumberTable_attribute?{
          ??u2?attribute_name_index;?u4?attribute_length;
          ??u2?line_number_table_length;
          ??{???u2?start_pc;?????//?字節(jié)碼指令區(qū)間開始位置
          ??????u2?line_number;??//?對應(yīng)的源碼行號
          ??}?line_number_table[line_number_table_length];
          }
          • LocalVariableTable 屬性:記錄 Java 方法中局部變量的變量名,與棧幀局部變量表中的變量的對應(yīng)關(guān)系,用于保留各方法有意義的變量名稱
          LocalVariableTable_attribute?{
          ??u2?attribute_name_index;?u4?attribute_length;
          ??u2?local_variable_table_length;
          ??{???u2?start_pc;?//?局部變量生命周期開始的字節(jié)碼偏移量
          ??????u2?length;???//?向后生命周期覆蓋的字節(jié)碼長度
          ??????u2?name_index;???????//?變量名
          ??????u2?descriptor_index;?//?類型描述符
          ??????u2?index;?//?對應(yīng)的局部變量表中的?slot?索引
          ??}?local_variable_table[local_variable_table_length];
          }

          其他屬性直接參考 JVM 文檔


          26、示例

          源碼:com/cls/Structure.java

          package?com.cls;

          public?class?Structure?{
          public?static?void?main(String[]?args)?{
          ??System.out.println("hello?world");
          }
          }

          javac -g:lines com/cls/Structure.java?編譯后,參考 javap 反編譯得到的正確結(jié)果,od -x --endian=big Structure.class?得出 class 文件內(nèi)容的十六進(jìn)制表示,解讀如下:

          cafe?babe?#?1.??u4?魔數(shù),標(biāo)識?class?文件類型
          0000?0034?#?2.??u2,u2?版本號,52?JDK8?

          #?3.?常量池
          ---1---
          001f?#?u2?constant_pool_count,31?項(xiàng)(從?1?開始計(jì)數(shù),0?預(yù)留)
          0a??????#?u1?tag,10,Methoddef_info,成員方法結(jié)構(gòu)?
          0006????#?u2?index,6,所屬類的?Class_info?在常量池中的編號???##?java/lang/Object
          0011????#?u2?index,17,此方法?NameAndType?編號?????????????##?:()V

          ---2---
          09??????#?9,F(xiàn)ileddef_info,成員變量結(jié)構(gòu)
          0012????#?u2?index,18,所屬類?Class_info?編號?????##?java/lang/System
          0013????#?u2?index,19,此字段?NameAndType?編號????##?out:Ljava/io/PrintStream

          ---3---
          08??????#?8,String_info,字符串
          0014????#?u2?index,20,字面量編號?????##?hello?world

          ---4---
          0a?
          0015????#?21????##?java/io/PrintStream
          0016????#?22????##?println:(Ljava/lang/String;)V

          ---5---
          07??????#?Class_info,全限定類名
          0017????#?u2?index,23,字面量編號?????##?com/cls/Structure

          ---6---
          07??????#?7,Class_info,類引用
          0018????#?24????##?java/lang/Object

          ---7---
          01??????#?Utf8_info,UTF8?編碼的字符串
          0006????#?u2?length,6,字符串長度
          3c?69?6e?69?74?3e?#?字面量值????##?""

          ---8-16---
          01?0003?282956??????????????????????????????????????????##?"()V"
          01?0004?436f6465????????????????????????????????????????##?"Code"
          01?000f?4c696e654e756d6265725461626c65??????????????????##?"LineNumberTable"
          01?0004?6d61696e????????????????????????????????????????##?"main"
          01?0016?285b4c6a6176612f6c616e672f537472696e673b2956????##?"([Ljava/lang/String;)V"
          01?0010?4d6574686f64506172616d6574657273????????????????##?"MethodParameters"
          01?0004?61726773????????????????????????????????????????##?"args"
          01?000a?536f7572636546696c65????????????????????????????##?"SourceFile"
          01?000e?5374727563747572652e6a617661????????????????????##?"Structure.java"

          ---17---
          0c??????#?12,NameAndType,名字及類型描述符
          0007????#?u2?index,7,字段或方法名字面量編號????##?
          0008????#?u2?index,8,字段或方法結(jié)構(gòu)編號???????##?()V

          ---18---
          07?0019?#?25????##?java/lang/System

          ---19---
          0c
          001a?001b???#?26:27????##?out:Ljava/io/PrintStream;

          ---20---
          01?000b?68656c6c6f20776f726c64????##?"hello?world"

          ---21--
          07?001c?#?28????##?java/io/PrintStream

          ---22--
          0c
          001d?001e???#?29:30?????????????????????????????##?println:(Ljava/lang/String;)V

          ---23-31---
          01?0011?636f6d2f636c732f537472756374757265??????????##?"com/cls/Structure"
          01?0010?6a6176612f6c616e672f4f626a656374????????????##?"java/lang/Object?"
          01?0010?6a6176?612f?6c61?6e67?2f53?7973?7465?6d?????##?"java/lang/System"
          01?0003?6f7574??????????????????????????????????????##?"out"
          01?0015?4c6a6176612f696f2f5072696e7453747265616d3b??##?"Ljava/io/PrintStream;"
          01?0013?6a6176612f696f2f5072696e7453747265616d??????##?"java/io/PrintStream"
          01?0007?7072696e746c6e??????????????????????????????##?"println"
          01?0015?284c6a6176612f6c616e672f537472696e673b2956??##?"(Ljava/lang/String;)V"

          0021?#?4.?u2,access_flags???????????????????????????##?ACC_PUBLIC?|?ACC_SUPER
          0005?#?5.?u2,?this_class,5???????????????????????????##?--5.Class_info-->?com/cls/Structure
          0006?#?6.?u2,?super_class,?6?????????????????????????##?--6.Class_info-->?java/lang/Object?
          0000?#?7.?u2,?interface_count,?0
          0000?#?8.?u2,?fields_count,?0

          0002?#?9.?methods?count,?2
          ??#?方法一
          0001????#?u2,?access_flags,?ACC_PUBLIC?????????????????
          0007????#?u2,?name_index,?7?????????????????????????##??
          0008????#?u2,?descriptor_index,?8???????????????????##?()V??????????????????????????
          0001????#?u2,?attribute_count,?1?????????????????????????????
          0009????????#?u2,?attribute_name_index,?9???????????##?Code?屬性
          0000?001d???#?u4,?attribute_length,?30?
          0001????????#?u2,?max_stack,?1????????????????????????????????
          0001????????#?u2,?max_locals,?1????????????????????????????
          0000?0005??#?u4,?code_array_length,?5????????????????????????
          2a???????????????#?u1,?aload_0???????????????????????##?將第?0?個(gè)?slot?中的變量?this?入棧?
          b7???0001????????#?u1,?invokespecial?????????????????##?執(zhí)行從?Object?繼承的?
          b1???????????????#?u1,?return????????????????????????##?返回?void
          0000????????#?u2,?exception_table_length,?0??????????##?exception?table?為空,無異常
          0001????????#?u2,?attributes_count,?1????????????????##?Code?屬性本身的子屬性
          000a????????????#?10????????????????????????????????????##?LineNumberTable?屬性
          0000?0006???????#?6
          0001????????????#?u2,?line_number_table_length,?1
          0000????????????????#?u2,?start_pc,?0
          0003????????????????#?u2,?line_number,?3
          ??#?方法二
          0009????#?access_flags???????????????????????????????##?ACC_PIBLIC?|?ACC_STATIC
          000b????#?name_index,?11?????????????????????????????##?main
          000c????#?descriptor_index,?12???????????????????????##?([Ljava/lang/String;)V
          0002????#?attribute_count,?2
          0009????????#?attribute_name_index,?9????????????????##?Code
          0000?0025??#?attribute_length,?37
          0002????????#?max_stack,?2?
          0001????????#?max_locals,?1
          0000?0009??#?code_array_length,?9
          b2???0002???????#?getstatic,?2???????????????????????##?Field:?java/lang/System.out:Ljava/io/PrintStream;?//?加載靜態(tài)對象變量
          12???03?????????#?ldc,?3?????????????????????????????##?String:?"hello?world"??//?將常量參數(shù)入棧
          b6???0004???????#?invokevirtual,?4???????????????????##?Method:?java/io/PrintStream.println:(Ljava/lang/String;)V?//?執(zhí)行方法
          b1??????????????#?return
          0000????????#?exception_table_length,?0
          0001????????#?attributes_count,?1
          000a????????#?10?????????????????????????????????????##?LineNumberTable
          0000?000a???#?10
          0002????????????#?line_number_table_length,?2
          0000?0005???????????#?0?->?5
          0008?0006???????????#?8?->?6

          27、字節(jié)碼指令

          JVM 面向操作數(shù)棧(operand stack)設(shè)計(jì)了指令集,每個(gè)指令由 1 字節(jié)的操作碼(opcode)表示,其后跟隨 0 個(gè)或多個(gè)操作數(shù)(operand),指令集列表參考 Java bytecode instruction listings

          • 大部分與數(shù)據(jù)類型相關(guān)的指令,其操作碼符號都會帶類型前綴,如 i 前綴表示操作 int,剩余對應(yīng)關(guān)系為?b:byte, c:char, s:short, f:float, d:double, l:long, a:reference
          • 由于指令集大小有限(256個(gè)),故?boolean, byte, char, short?會被轉(zhuǎn)為int運(yùn)算

          字節(jié)碼可大致分為六類:

          • 加載和存儲指令:將變量從局部變量表 slot 加載到操作數(shù)棧的棧頂,反向則是存儲
          //?將?slot?0,1,2,3,N?加載到棧頂,T?表示類型簡記前綴,可取?i,l,f,d,a
          Tload_0,?Tload_1,?Tload_2,?Tload_3,?Tload?n
          //?將棧頂數(shù)據(jù)寫回指定的?slot
          Tstore_0,?Tstore_1,?Tstore_2,?Tstore_3,?Tstore?n
          //?將不同范圍的常量值加載到棧頂,由于?0~5?常量過于常用,有單獨(dú)對應(yīng)的指令,ldc?則加載普通常量
          bipush,?sipush,?Tconst_[0,1,2,3,4,5],?aconst_null,?ldc
          • 運(yùn)算指令
          Tadd, Tsub, Tmul, Tdiv, Trem ????//?算術(shù)運(yùn)算:加減乘除,取余
          Tneg, Tor, Tand, Txor ???????????//?位運(yùn)算:取反、或、與、異或
          dcmpg, dcmpl, fcmpg, fcmpl, lcmp //?比較運(yùn)算:后綴 g 即 greater, l 即 less than
          iinc?????????????????????????????//?局部自增運(yùn)算,與?iload?搭配使用
          • 強(qiáng)制類型轉(zhuǎn)換指令:窄化轉(zhuǎn)換為 T 類型(長度為 N)時(shí),會直接丟棄除了低 N 位外的其他位,可能會導(dǎo)致數(shù)據(jù)溢出、正負(fù)號不確定,浮點(diǎn)數(shù)轉(zhuǎn)整型則會丟失精度
          i2b?//?int?->?byte
          i2c,?i2s;?l2i,?f2i,?d2i;?d2l,?f2l;?d2f
          • 對象創(chuàng)建與訪問指令:類實(shí)例、數(shù)組都是對象,存儲結(jié)構(gòu)不同,創(chuàng)建和訪問指令有所區(qū)別
          new??????????????????????????????????????//?創(chuàng)建類實(shí)例
          newarray,?annewarray,?multianewarry??????//?創(chuàng)建基本類型數(shù)組、引用類型數(shù)組、多維引用類型數(shù)組
          getfield, putfield; getstatic, putstatic //?讀寫類實(shí)例字段;讀寫類靜態(tài)字段
          Taload, Tastore; arraylength ????????????//?讀寫數(shù)組元素;計(jì)算數(shù)組長度
          instanceof; checkcast ???????????????????//?校驗(yàn)對象是否為類實(shí)例;執(zhí)行強(qiáng)制轉(zhuǎn)換
          • 操作數(shù)棧管理指令
          pop,?pop2???????//?彈出棧頂?1,2?元素
          dup, dup2; swap //?復(fù)制棧頂 1,2 個(gè)元素并重新入棧;交換棧頂兩個(gè)元素
          • 控制轉(zhuǎn)移指令:判斷條件成立,則跳轉(zhuǎn)到指定的指令行(修改 PC 指向)
          if_?//?整型比較,引用相等性判斷
          if?????????????????????????????//?搭配其他類型的比較運(yùn)算指令使用
          • 方法調(diào)用與返回指令
          invokevirtual???//?根據(jù)對象的實(shí)際類型進(jìn)行分派,調(diào)用對應(yīng)的方法(比如繼承后方法重寫)?
          invokespecial???//?調(diào)用特殊方法,如?()V,?()V?等初始化方法、私有方法、父類方法
          invokestatic????//?調(diào)用類的靜態(tài)方法
          invokeinterface?//?調(diào)用接口方法(實(shí)現(xiàn)接口的類對象,但被聲明為接口類型,調(diào)用方法)
          invokedynamic???//?TODO
          Treturn,?return?//?返回指定類型,返回?void
          • 異常處理指令:athrow?拋出異常,異常處理則由 exception_table 描述

          • 同步指令:synchronized 對象鎖由?monitorenter, monitorexit?搭配對象的 monitor 鎖共同實(shí)現(xiàn)


          28、類加載

          29、類加載過程

          1. 加載

          原理:委托 ClassLoader 讀取 Class 二進(jìn)制字節(jié)流,載入到方法區(qū)內(nèi)存,并在堆內(nèi)存中生成對應(yīng)的java.lang.Class對象相互引用

          2. 驗(yàn)證

          校驗(yàn)字節(jié)流確保符合 Class 文件格式,執(zhí)行語義分析確保符合 Java 語法,校驗(yàn)字節(jié)碼指令合法性

          3. 準(zhǔn)備

          在堆中分配類變量(static)內(nèi)存并初始化為零值,主義還沒到執(zhí)行 putstatic 指令賦值的初始化階段,但靜態(tài)常量屬性除外:

          public?class?ClassX?{
          final?static?int?n?=?2;??????????//?常量的值在編譯期就已知,準(zhǔn)備階段完成賦值,值存儲在?ConstantValue
          final?static?String?str?=?"str";?//?字符串靜態(tài)常量同理
          }

          static?final?java.lang.String?str;
          descriptor:?Ljava/lang/String;
          flags:?ACC_STATIC,?ACC_FINAL
          ConstantValue:?String?str

          4. 解析

          將常量池中的符號引用(Class_info, Fieldref_info, Methodref_info)替換為直接引用(內(nèi)存地址)

          5. 初始化

          javac 會從上到下合并類中 static 變量賦值、static 語句塊,生成類構(gòu)造器()V,在初始化階段執(zhí)行,此方法的執(zhí)行由 JVM 保證線程安全;注意 JVM 規(guī)定有且僅有的,會立即觸發(fā)對類初始化的六種 case

          public?class?ClassX?{
          static?{
          ??println("main?class?ClassX?init");?//?1.?main()?所在的主類,總是先被初始化
          }

          public?static?void?main(String[]?args)?throws?Exception?{
          ??//?首次會觸發(fā)類的初始化
          ??//?SubX?b?=?new?SubX();??//?new?對象?//?2.?new,?getsatic,?putstatic,?invokestatic?指令
          ??//?println(SuperX.a);????//?讀寫類的?static?變量,或調(diào)用?static?方法?
          ??//?println(SubX.c);??????//?3.?子類初始化,會觸發(fā)父類初始化
          ??//?println(SubX.a);??????//????子類訪問父類的靜態(tài)變量,只會觸發(fā)父類初始化
          ??
          ??//?不會觸發(fā)類的初始化
          ??//?println(SubX.b);??????//?1.?訪問類的靜態(tài)常量(基本類型、字符串字面量)
          ??//?println(SubX.class);??//?2.?訪問類對象
          ??//?println(new?SubX[2]);?//?3.?創(chuàng)建類的數(shù)組
          }
          }

          class?SuperX?{
          static?int?a?=?0;
          static?{
          ??println("class?SuperX?initiated");
          }
          }

          class?SubX?extends?SuperX?{
          final?static?double?b?=?0.1;
          static?boolean?c?=?false;
          static?{
          ??println("class?SubX?initiated");
          }
          }

          30、類加載器

          層級關(guān)系

          雙親委派機(jī)制

          • 原理:一個(gè)類加載器收到加載某個(gè)類的請求時(shí),會先委派上層的父類加載器去加載,逐層向上,當(dāng)父類加載器逐層向下反饋都無法加載此類后,該類加載器才會嘗試自己加載;此模型保證了,諸如 rt.jar 中的java.lang.Object類,不論在底層哪種類加載器中都一定是被 Bootstrap 類加載器加載, JVM 中僅此一份,保證了一致性

          • 實(shí)現(xiàn)

          //?java/lang/ClassLoader
          protected?Class?loadClass(String?name,?boolean?resolve)?throws?ClassNotFoundException?{
          ????synchronized?(getClassLoadingLock(name))?{
          ????????//?1.?先檢查自己的加載器是否已加載此類
          ????????Class?c?=?findLoadedClass(name);
          ????????if?(c?==?null)?{
          ????????????long?t0?=?System.nanoTime();
          ????????????try?{
          ????????????????if?(parent?!=?null)?{
          ????????????????????//?2.?還有上層則委派給上層去加載
          ????????????????????c?=?parent.loadClass(name,?false);
          ????????????????}?else?{
          ????????????????????//?3.?如果沒有上級,則委派給?Bootstrap?加載
          ????????????????????c?=?findBootstrapClassOrNull(name);
          ????????????????}
          ????????????}?catch?(ClassNotFoundException?e)?{
          ????????????????//?類不存在
          ????????????}

          ????????????if?(c?==?null)?{
          ????????????????//?4.?到自己的?classpath?中查找類,用戶自定義?ClassLoader?自定義了查找規(guī)則
          ????????????????long?t1?=?System.nanoTime();
          ????????????????c?=?findClass(name);
          ????????????}
          ????????}
          ????????if?(resolve)?{
          ????????????resolveClass(c);
          ????????}
          ????????return?c;
          ????}
          }

          31、字節(jié)碼執(zhí)行引擎

          32、運(yùn)行時(shí)棧幀結(jié)構(gòu)

          public?static?void?main(String[]?args)?{
          int?a?=?1008611;
          int?b?=?++a;
          }

          對應(yīng)運(yùn)行時(shí)棧幀結(jié)構(gòu):

          • 局部變量表:大小在編譯期確定,用于存放實(shí)參和局部變量,以大小為 32 bit 的變量槽為最小單位

          • long, double 類型被切分為 2 個(gè) slot 同時(shí)讀寫(單線程操作,無線程安全問題)

          • 類對象調(diào)用方法時(shí),slot 0 固定為當(dāng)前對象的引用,即this隱式實(shí)參

          • 變量槽有重用優(yōu)化,當(dāng) pc 指令超出某個(gè)槽中的變量的作用域時(shí),該槽會被其他變量重用

          public?static?void?main(String[]?args)?{
          ??{
          ??????byte[]?buf?=?new?byte[10?*?1024?*?1024];
          ??}
          ??System.gc();??//?buf?還在局部變量表的?slot?0?中,作為?GC?Root?無法被回收
          ??//?int?v?=?0;?//?變量?v?重用?slot?0,gc?生效
          ??//?System.gc();?
          • 操作數(shù)棧:最大深度在編譯期確定,與局部變量表配合入棧、執(zhí)行指令、出棧來執(zhí)行字節(jié)碼指令

          • 返回地址:遇到return?族指令則正常調(diào)用完成,發(fā)生異常但異常表中沒有對應(yīng)的 handler 則異常調(diào)用完成;正常退出到上層方法后,若有返回值則壓入棧,并將程序計(jì)數(shù)器恢復(fù)到方法調(diào)用指令的下一條指令


          33、方法調(diào)用

          34、虛方法、非虛方法

          非虛方法:編譯期可知(程序運(yùn)行前就唯一確定)、且運(yùn)行期不可變的方法,在類加載階段就會將方法的符號引用解析為直接引用。有 5 種:

          • 靜態(tài)方法(與類唯一關(guān)聯(lián)):invokestatic調(diào)用
          • 私有方法(外部不可訪問)、構(gòu)造器方法、父類方法:invokespecial調(diào)用
          • final 方法(無法被繼承):由invokevirtual調(diào)用(歷史原因)
          public?class?StaticResolution?{
          ?public?static?void?doFunc()?{
          ??System.out.println("do?func...");
          ?}
          ?public?static?void?main(String[]?args)?{
          ??StaticResolution.doFunc();
          ?}
          }

          stack=0,?locals=1,?args_size=1????//?靜態(tài)方法的調(diào)用版本,在編譯時(shí)就以常量的形式,存入字節(jié)碼的參數(shù)
          0:?invokestatic??#5???????????//?Method?doFunc:()V
          ??3:?return

          虛方法:需在運(yùn)行時(shí)動態(tài)確定直接引用的方法,由invokevirtual, invokeinterface調(diào)用

          35、靜態(tài)分派、動態(tài)分派

          背景:方法可被重載(參數(shù)類型不同,或數(shù)量不同)、可被重寫(子類繼承后覆蓋)

          分派:對象可聲明為類、父類、實(shí)現(xiàn)的接口等類型,當(dāng)對象作為實(shí)參或調(diào)用方法時(shí),需根據(jù)其靜態(tài)類型或?qū)嶋H類型,才能確定要調(diào)用的方法的版本,進(jìn)而確定其直接引用。此過程即方法的分派

          reference 變量的 2 種類型

          • 靜態(tài)類型:變量被聲明的類型,不會改變,編譯期可知
          • 實(shí)際類型:變量指向的對象可被替換,運(yùn)行時(shí)隨時(shí)可能修改

          靜態(tài)分派

          • 原理:方法重載時(shí),依賴參數(shù)的靜態(tài)類型,來確定要使用哪個(gè)重載版本的方法
          • 特點(diǎn):發(fā)生在編譯階段,由 javac 確定類型“匹配度最高的”重載版本,來作為invokevirtual的參數(shù)
          public?class?StaticDispatch?{
          ?static?abstract?class?Human?{}
          ?static?class?Man?extends?Human?{}
          ?static?class?Woman?extends?Human?{?}

          ?public?void?f(Human?human)?{System.out.println("f(Human)");}
          ?public?void?f(Man?man)?{System.out.println("f(Man)");}
          ?public?void?f(Woman?woman)?{System.out.println("f(Woman)");}

          ?public?static?void?main(String[]?args)?{
          ??Human?man?=?new?Man();?????//?靜態(tài)類型都是?Human
          ??Human?woman?=?new?Woman();?//?實(shí)際類型分別為?Man,?Woman
          ??StaticDispatch?sd?=?new?StaticDispatch();
          ??sd.f(man);???//?f(Human)?//?invokevirtual?#13?//?Method?f:(Lcom/ch08/StaticDispatch$Human;)V
          ??sd.f(woman);?//?f(Human)?//?編譯期就已確定重載版本,寫入字節(jié)碼中
          ?}
          }

          動態(tài)分派

          • 原理:方法重寫時(shí),依賴 Receiver 對象的實(shí)際類型,來確定要使用哪個(gè)類版本的方法

          • 特點(diǎn):發(fā)生在運(yùn)行時(shí),依賴invokevirtual指令來確定調(diào)用方法的版本,進(jìn)而實(shí)現(xiàn)多態(tài),解析流程為

          注:類的方法查找是高頻操作,JVM 會在方法區(qū)中為類建一張?zhí)摲椒ū?vtable,以實(shí)現(xiàn)方法的快速查找

          public?class?DynamicDispatch?{
          static?abstract?class?Human?{
          ????protected?abstract?void?f();
          }

          static?class?Man?extends?Human?{
          ????@Override
          ????protected?void?f()?{
          ????????System.out.println("Man?f()");
          ????}
          }

          static?class?Woman?extends?Human?{
          ????@Override
          ????protected?void?f()?{
          ????????System.out.println("Woman?f()");
          ????}
          }

          public?static?void?main(String[]?args)?{
          ????Human?man?=?new?Man();?//?雖然靜態(tài)類型都是?Human
          ????Human?woman?=?new?Woman();
          ????man.f();???//?Man?f()???//?invokevirtual?#6?//?Method?com/ch08/DynamicDispatch$Human.f:()V
          ????woman.f();?//?Woman?f()?//?雖然字節(jié)碼指令的參數(shù),都是靜態(tài)類型方法的符號引用
          ????man?=?new?Woman();
          ????man.f();?//?Woman?f()?//?但?invokevirtual?會根據(jù)?Receiver?實(shí)際類型,在運(yùn)行時(shí)解析到實(shí)際類的直接引用
          }
          }

          注意,類的字段讀寫指令getfield, putfield沒有invokevirtual的動態(tài)分派機(jī)制,即子類的同名字段會直接覆蓋父類的字段。示例:

          public?class?FieldHasNoPolymorphic?{
          static?class?Father?{
          ????public?int?money?=?1;
          ????public?Father()?{
          ????????money?=?2;
          ????????showMoney();
          ????}
          ????public?void?showMoney()?{?System.out.println("Father,?money?=?"?+?money);?}
          }

          static?class?Son?extends?Father?{
          ????public?int?money?=?3;?//?子類字段在類加載的準(zhǔn)備階段被賦零值
          ????public?Son()?{?//?子類構(gòu)造器第一行默認(rèn)隱藏調(diào)用?super()
          ????????money?=?4;
          ????????showMoney();
          ????}
          ????public?void?showMoney()?{?System.out.println("Son,?money?=?"?+?money);?}
          }

          public?static?void?main(String[]?args)?{
          ????Father?guy?=?new?Son();?
          ????System.out.println("guy,?money?=?"?+?guy.money);
          }
          }

          //?Son,?money?=?0?//?Father?類構(gòu)造器執(zhí)行,動態(tài)分派執(zhí)行了?Son::showMoney()
          //?Son,?money?=?4?//?Son?類構(gòu)造器中訪問最新的、自己的?money?字段
          //?guy,?money?=?2?//?字段的讀寫沒有動態(tài)分派,靜態(tài)類型是誰,就訪問誰的字段

          36、單分派、多分派

          方法的 Receiver,與方法的參數(shù),都是方法的宗量,根據(jù)一個(gè)宗量來選擇目標(biāo)方法稱為單分派,需要多個(gè)宗量才能確定方法的叫多分派

          • 靜態(tài)分派機(jī)制會讓編譯器在編譯階段,對重載的多個(gè)方法,會選出參數(shù)匹配度最高的作為目標(biāo)方法
          • 動態(tài)分派機(jī)制在運(yùn)行時(shí),依賴 Receiver 實(shí)際類型,配合invokevirtual定位唯一的實(shí)例方法作為目標(biāo)方法

          綜上兩點(diǎn),Java 是靜態(tài)多分派、動態(tài)單分派的語言


          注明:第 10,11 章講 Java 的前后端編譯,學(xué)習(xí)了自動裝箱等常見語法糖的字節(jié)碼實(shí)現(xiàn),其余部分待有空搭配龍書一起學(xué);第 12,13 章內(nèi)容與《Java Concurrency In Practice》等書重合度較高,此處不再贅述


          37、總結(jié)

          學(xué)習(xí)《深入理解 JVM 3ed》,初步掌握了 JVM 內(nèi)存區(qū)域的劃分模型、GC 算法理論及常見回收器原理、Class 文件結(jié)構(gòu)中各數(shù)據(jù)項(xiàng)解釋、類加載流程、方法的執(zhí)行與分派等五大方面的知識,收獲頗豐。不過大部分都是理論,若有機(jī)會還是要研究下 openjdk 的源碼實(shí)現(xiàn) :(

          來源 |?yinzige.com/2020/03/08/understanding-jvm-3ed/

          后臺回復(fù)?學(xué)習(xí)資料?領(lǐng)取學(xué)習(xí)視頻


          瀏覽 129
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产菊门残忍扩张视频 | 麻豆三级片 | 国产成人无码A片免费看玄火 | 免费操比视频 | 91麻豆精品 |