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

          JVM基礎(chǔ)及內(nèi)存區(qū)域

          共 9879字,需瀏覽 20分鐘

           ·

          2021-06-29 17:37

          JVM基礎(chǔ)知識(shí)

          首先要了解JVM的基礎(chǔ)知識(shí),知道JVM在Java中起到了什么作用?以及JVM的一些概念。

          Java從編譯到執(zhí)行

          c385e3518007ed81a7a67f59c5b30b17.webpjava從編譯到執(zhí)行:java文件通過javac編譯成class文件,通過JVM中ClassLoader類加載器執(zhí)行class文件,一般會(huì)字節(jié)碼解析器執(zhí)行也可能會(huì)通過JIT編譯器執(zhí)行,通過執(zhí)行引擎編譯成機(jī)器碼,由硬件處理。Java文件 -> 編譯器 -> 字節(jié)碼 -> JVM -> 機(jī)器碼

          JDK、JVM、JRE區(qū)別

          • JVM:JVM只是一個(gè)翻譯,把class文件翻譯成機(jī)器碼,JVM不會(huì)自己生成代碼,需要自己編寫代碼,同時(shí)還需要很多依賴類庫,這時(shí)就需要用到JRE了。
          • JRE:JRE除了包含JVM之外,還提供了很多類庫,也就是很多jar包,它提供了一些即插即用的功能比如文件操作、連接網(wǎng)絡(luò)、I/O操作等,這些都是JRE提供的基礎(chǔ)類庫。JVM標(biāo)準(zhǔn)加上實(shí)現(xiàn)的一大堆基礎(chǔ)類庫,就組成了Java的運(yùn)行時(shí)環(huán)境(Java Runtime Environment)JRE
          • JDK:對(duì)于服務(wù)器可能只需要JRE就可以了,但是對(duì)于程序員只有JRE還不夠,程序員要寫完代碼、編譯代碼、調(diào)試代碼、打包代碼,甚至有時(shí)候還需要反編譯代碼,這時(shí)候就需要使用JDK,JDK包含了:javac(編譯代碼)、java、jar(打包代碼)、javap(反編譯)。

          JVM的跨平臺(tái)性與語言無關(guān)性

          JVM 不識(shí)別.java文件而是識(shí)別.class文件字節(jié)碼,所以JVM和語言是解耦的,可以編譯成class的語言都可以在JVM上運(yùn)行。JVM只識(shí)別字節(jié)碼,JVM和語言解耦的,沒有直接關(guān)聯(lián),所以JVM是和語言無關(guān)的。注意JVM運(yùn)行不是翻譯Java文件,而是識(shí)別class文件,比如Scala、kotlin、groovy它們都可以編譯成字節(jié)碼文件,所以可以在JVM上跑,這也就是說JVM是跨平臺(tái)的。047f66a8b693ab9f59a703d9058d4baa.webpJVM的跨平臺(tái)性和語言無關(guān)性,如下圖所示:編譯成字節(jié)碼交給JVM處理為機(jī)器碼,在設(shè)備上運(yùn)行5f167712bf9f35e20f39b2117a52e967.webp

          常見的JVM實(shí)現(xiàn)

          998b8201af692396cef12c6cf738d5fd.webpimage.png
          • Hotspot: 目前使用最多的虛擬機(jī),可以通過執(zhí)行java --version 查看你現(xiàn)在使用虛擬機(jī)的名字
          f7a735e3ff4c203b19e1066d73e102c3.webpimage.png
          • J9: IBM 有自己的 java 虛擬機(jī)實(shí)現(xiàn),它的名字叫做 J9. 主要是用在 IBM 產(chǎn)品(IBM WebSphere 和 IBM 的 AIX 平臺(tái)上)
          • TaobaoVM只有一定體量、一定規(guī)模的廠商才會(huì)開發(fā)自己的虛擬機(jī),比如淘寶有自己的 VM,它實(shí)際上是 Hotspot 的定制版,專門為淘寶準(zhǔn)備的,阿里、天 貓都是用的這款虛擬機(jī)。
          • LiquidVM它是一個(gè)針對(duì)硬件的虛擬機(jī),它下面是沒有操作系統(tǒng)的(不是 Linux 也不是 windows),下面直接就是硬件,運(yùn)行效率比較高。
          • zing它屬于 zual 這家公司,非常牛,是一個(gè)商業(yè)產(chǎn)品,很貴!它的垃圾回收速度非常快(1 毫秒之內(nèi)),是業(yè)界標(biāo)桿。它的一個(gè)垃圾回收的算法后來被Hotspot 吸收才有了現(xiàn)在的 ZGC。

          JVM的知識(shí)模塊

          82c2654a8fe3a27bd3c1549fa228084f.webpJVM有非常龐大的知識(shí)體系:比如內(nèi)存結(jié)構(gòu)、垃圾回收、類加載、執(zhí)行引擎、類文件結(jié)構(gòu)、監(jiān)控工具、性能調(diào)優(yōu)等等。學(xué)習(xí)JVM要把握重點(diǎn),在JVM的所有知識(shí)體系中或多或少都和內(nèi)存結(jié)構(gòu)有關(guān),比如垃圾回收回收的就是內(nèi)存、類加載加載到的地方也是內(nèi)存、性能優(yōu)化也涉及內(nèi)存、執(zhí)行引擎和內(nèi)存密不可分、類文件結(jié)構(gòu)和內(nèi)存設(shè)計(jì)有關(guān)、監(jiān)控工具也會(huì)監(jiān)控內(nèi)存,JVM也是一個(gè)虛擬化的操作系統(tǒng),處理虛擬指令外,還需要虛擬化的內(nèi)存,而這個(gè)虛擬化的內(nèi)存就是JVM的內(nèi)存區(qū)域。學(xué)習(xí)JVM首先從內(nèi)存結(jié)構(gòu)開始。

          JVM的內(nèi)存區(qū)域

          jvm內(nèi)存區(qū)域圖:3f98d9e04d8d049bbf1cad71747d13f9.webp

          JVM 是Java虛擬機(jī),類似一個(gè)操作系統(tǒng),class就是指令,比如一個(gè)操作系統(tǒng)有8G的內(nèi)存,其中3G為虛擬內(nèi)存(運(yùn)行時(shí)數(shù)據(jù)區(qū))剩下的5G可以理解為JVM的直接內(nèi)存,這個(gè)虛擬內(nèi)存就是JVM的運(yùn)行時(shí)數(shù)據(jù)區(qū)域,另外還有一個(gè)直接內(nèi)存不是運(yùn)行時(shí)數(shù)據(jù)區(qū)域的一部分,但是會(huì)頻繁使用。

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

          運(yùn)行時(shí)數(shù)據(jù)區(qū)域:Java虛擬機(jī)在執(zhí)行Java程序的過程中會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域

          • 線程私有區(qū)域
            • 虛擬機(jī)棧
            • 本地方法棧
            • 程序計(jì)數(shù)器
          • 線程共享區(qū)
            • 運(yùn)行時(shí)常量池
            • 方法區(qū)

          虛擬機(jī)棧

          存儲(chǔ)當(dāng)前線程運(yùn)行Java方法所需的數(shù)據(jù),指令、返回地址

          虛擬機(jī)棧中主要包括棧幀,而棧幀包括:局部變量、操作數(shù)棧、動(dòng)態(tài)鏈接、完成出口 在實(shí)際代碼中,一個(gè)線程是可以運(yùn)行多個(gè)方法的。如下代碼:main -> A -> B -> C, 運(yùn)行代碼,線程1來運(yùn)行,就會(huì)有一個(gè)對(duì)應(yīng)的虛擬機(jī)棧,同時(shí)在執(zhí)行每個(gè)方法的時(shí)候都會(huì)打包成一個(gè)棧幀。

          /**
          ?*?虛擬機(jī)棧
          ?*/

          public?class?MouthedOrStack?{
          ????public?static?void?main(String[]?args)?{
          ????????A();
          ????}

          ????private?static?void?A()?{
          ????????B();
          ????}

          ????private?static?void?B()?{
          ????????C();
          ????}

          ????private?static?void?C()?{

          ????}
          }

          代碼的執(zhí)行過程:如下圖所示,執(zhí)行main()方法的時(shí)候入棧,執(zhí)行A()方法的時(shí)候入棧.... 當(dāng)C()方法執(zhí)行完了,C()方法出棧,接著B方法運(yùn)行完了出棧,A方法運(yùn)行完了出棧,最后main方法執(zhí)行完了出棧。這個(gè)就是Java方法運(yùn)行對(duì)虛擬機(jī)棧的一個(gè)影響。虛擬機(jī)棧就是用來存儲(chǔ)線程運(yùn)行方法中的數(shù)據(jù)的,每一個(gè)方法對(duì)應(yīng)一個(gè)棧幀1f3e4acd10625caaa53a69c59bd35498.webp:::tips 虛擬機(jī)棧是基于線程的,哪怕只有一個(gè)main()方法,也是以線程的方式運(yùn)行的,在線程的生命周期中,參與計(jì)算的數(shù)據(jù)會(huì)頻繁地入棧和出棧,棧的生命周期和線程一樣的 :::

          棧的大小限制:-Xss 設(shè)置線程堆棧大小,不同的操作系統(tǒng),不同的位數(shù)虛擬機(jī)棧的大小是不同的。查看jvm的設(shè)置

          -Xsssize
          Sets?the?thread?stack?size?(in?bytes).?Append?the?letter?k?or?K?to?indicate?KB,?m?or?M?to?indicate?MB,?g?or?G?to?indicate?GB.?The?default?value?depends?on?the?platform:

          Linux/ARM?(32-bit):?320?KB

          Linux/i386?(32-bit):?320?KB

          Linux/x64?(64-bit):?1024?KB

          OS?X?(64-bit):?1024?KB

          Oracle?Solaris/i386?(32-bit):?320?KB

          Oracle?Solaris/x64?(64-bit):?1024?KB

          The?following?examples?set?the?thread?stack?size?to?1024?KB?in?different?units:

          -Xss1m
          -Xss1024k
          -Xss1048576
          This?option?is?equivalent?to?-XX:ThreadStackSize.

          虛擬機(jī)棧常見的錯(cuò)誤:棧溢出:StackOverflowError ? 常見的場景:一般無限循環(huán)遞歸會(huì)造成這個(gè)錯(cuò)誤 只有壓棧沒有彈出棧,虛擬機(jī)棧內(nèi)存也不是無限大的,它是有大小限制的如下代碼:

          /**
          ?*?棧異常
          ?*/

          public?class?StackError?{
          ????public?static?void?main(String[]?args)?{
          ????????A();
          ????}

          ????public?static?void?A()?{
          ????????A();
          ????}
          }

          虛擬機(jī)棧存儲(chǔ)的主要是棧幀

          什么是棧幀 (重點(diǎn)):::tips 在每個(gè)Java方法被調(diào)用的時(shí)候,都會(huì)創(chuàng)建一個(gè)棧幀,并入棧。一旦方法完成響應(yīng)的調(diào)用,則出棧 :::如下圖:fa1ff847d78125a73a749546b2d188ef.webp根據(jù)上述的講解,我們都知道虛擬機(jī)棧主要是存儲(chǔ)當(dāng)前線程運(yùn)行Java方法中的指令、數(shù)據(jù)、返回地址如上圖所示,那么每一個(gè)方法都是一個(gè)棧幀,而棧幀中存儲(chǔ)著方法中的變量數(shù)據(jù)、指令、返回等 棧幀主要包括:局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、返回地址。根據(jù)如下的代碼,來模擬一個(gè)方法調(diào)用后再棧幀中的處理過程

          public?class?OperandStack?{
          ????public?int?test()?{
          ????????int?a?=?0;
          ????????int?b?=?1;
          ????????int?z?=?(a?+?b)?*?10;
          ????????return?z;
          ????}

          ????public?static?void?main(String[]?args)?{
          ????????OperandStack?operandStack?=?new?OperandStack();
          ????????operandStack.test();
          ????}
          }

          下面我們來模擬一下上述代碼中的test方法是如何在虛擬機(jī)棧中運(yùn)行的。如下圖是一個(gè),默認(rèn)狀態(tài)的虛擬機(jī)棧,假設(shè)test()在線程1中執(zhí)行,可以看到局部變量表中有一個(gè)this這個(gè)是默認(rèn)的指向的當(dāng)前的對(duì)象。首先,我們需要有一個(gè)正確的認(rèn)知,既然JVM是Java的一個(gè)虛擬機(jī),那么JVM就具備一個(gè)操作系統(tǒng)所具備的核心功能:CPU + 主內(nèi)存 + 緩存,那么JVM是一個(gè)模擬版的操作系統(tǒng),JVM執(zhí)行引擎(CPU) + 棧、堆等(主內(nèi)存) + 操作數(shù)棧(緩存),首先要知道一個(gè)操作系統(tǒng)是如何執(zhí)行的,比如執(zhí)行1+1計(jì)算,那么這個(gè)計(jì)算是在CPU中執(zhí)行,直接結(jié)果放到緩存中的,那么JVM也是同樣的原理,JVM是通過執(zhí)行引擎計(jì)算,將計(jì)算結(jié)果存入操作數(shù)棧中。理解這個(gè)原理我們就可以很輕松的理解虛擬機(jī)棧的運(yùn)行過程。1068bf931447d65432675b137aa6c092.webp我們都知道JVM是處理class文件中的指令,那么我們需要把上述的Java源代碼,編譯成class文件,然后通過javap -c指令反匯編,來查看class文件中的指令 如下代碼就是編譯后的class字節(jié)碼文件

          public?class?course01.OperandStack?{
          ??public?course01.OperandStack();
          ????Code:
          ???????0:?aload_0
          ???????1:?invokespecial?#1??????????????????//?Method?java/lang/Object."<init>":()V
          ???????4:?return

          ??public?int?test();
          ????Code:
          ???????0:?iconst_0
          ???????1:?istore_1
          ???????2:?iconst_1
          ???????3:?istore_2
          ???????4:?iload_1
          ???????5:?iload_2
          ???????6:?iadd
          ???????7:?bipush????????10
          ???????9:?imul
          ??????10:?istore_3
          ??????11:?iload_3
          ??????12:?ireturn

          ??public?static?void?main(java.lang.String[])
          ;
          ????Code:
          ???????0:?new???????????#2??????????????????//?class?course01/OperandStack
          ???????3:?dup
          ???????4:?invokespecial?#3??????????????????//?Method?"<init>":()V
          ???????7:?astore_1
          ???????8:?aload_1
          ???????9:?invokevirtual?#4??????????????????//?Method?test:()I
          ??????12:?pop
          ??????13:?return
          }

          這里面涉及到了一些指令,這些指令不需要死記硬背,而是需要去理解,可以根據(jù)我提供的鏈接,直接查找這個(gè)指令的意思即可。首先執(zhí)行:int a = 0;在字節(jié)碼的指令中首先看到的是:iconst_0 這個(gè)指令是什么意思呢?直接從上述我提供的鏈接中查找, 意思就是將一個(gè)常量0加載到操作數(shù)棧,哦原來這個(gè)意思就是將int a = 0的常量值放到操作數(shù)棧中,這時(shí)候線程1中的虛擬機(jī)棧就變成了如下:操作數(shù)棧中多了一個(gè)常量0916521896a28fb02572306af72db8553.webp我們繼續(xù)往下走,一般指令都是按照順序執(zhí)行的,指令是不會(huì)跳躍執(zhí)行,所以我們繼續(xù)順著指令,下一個(gè)指令執(zhí)行的是:istore_1 查找istore指令是什么意思:講一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表中,哦原來是這個(gè)意思,就是從操作數(shù)棧的棧頂取值然后放到局部變量表中,不知道大家有沒有注意一個(gè)問題istore_1 為什么是1而不是0呢?這是因?yàn)榫植孔兞勘碇性?的位置默認(rèn)有一個(gè)this指向當(dāng)前的對(duì)象。(理解每一個(gè)步驟及參數(shù)的意義是很重要的) 這是JVM執(zhí)行istore_1這個(gè)指令,此時(shí)虛擬機(jī)棧中的情況:局部變量表的1的位置多了一個(gè)數(shù)值0的常量,此時(shí)操作數(shù)棧是沒有數(shù)據(jù)的。因?yàn)椴僮鲾?shù)棧的數(shù)據(jù)出棧存入到局部變量表中了。5892325c35e11711d2d2f9243892d002.webp那么剩下的兩個(gè)指令執(zhí)行,我相信就不用在細(xì)說了吧 ?2: iconst_1 將常量1存入操作數(shù)棧中3: istore_2 將操作數(shù)棧的數(shù)據(jù)存入到局部變量表中 此時(shí),虛擬機(jī)棧中的情況: 局部變量表中存儲(chǔ)著:this、0、1,操作數(shù)棧是空的,我們需要理解操作數(shù)棧其實(shí)就是緩存,主要用來臨時(shí)存儲(chǔ)計(jì)算結(jié)果的我們不可能在緩存中長久保存數(shù)據(jù)。(如果還不理解建議學(xué)習(xí)一下操作系統(tǒng)的基礎(chǔ))bdf57838d741431856915a9688c44a34.webpOK,繼續(xù)執(zhí)行指令:iload指令,這個(gè)指令是將一個(gè)局部變量加載到操作數(shù)棧iload_1 :將局部變量表中1的位置存儲(chǔ)的數(shù)據(jù),加載到操作數(shù)棧中iload_2 :將局部變量表中2的位置存儲(chǔ)的數(shù)據(jù),加載到操作數(shù)棧中 思考:為什么又要加載到操作數(shù)棧呢?例如CPU要計(jì)算數(shù)據(jù),需要從緩存中拿取數(shù)據(jù)計(jì)算,然后將計(jì)算結(jié)果存入到緩存中。JVM的操作也是同樣的原理 此時(shí),虛擬機(jī)棧中的運(yùn)行情況:d4e421617dc0f65cddcaca0fc141efbe.webp繼續(xù)執(zhí)行指令:iadd : 算法指令 用于對(duì)兩個(gè)操作數(shù)棧上的數(shù)值進(jìn)行某種特定的運(yùn)算,并把結(jié)果重新存入到操作數(shù)棧頂 此時(shí)虛擬機(jī)棧的運(yùn)行情況:首先從操作數(shù)棧取出兩個(gè)數(shù)據(jù),由執(zhí)行引擎進(jìn)行計(jì)算:(a + b) 得到的結(jié)果1存入到操作數(shù)棧的棧頂d5540fd9eaff6f60c2aa4f55ed95e9b9.webp繼續(xù)執(zhí)行指令:bipush ? ? ? ?10 :把一個(gè)數(shù)值推送到操作數(shù)棧棧頂,這里其實(shí)執(zhí)行的代碼就相當(dāng)于:(a+b)*10 bipush指令直接將數(shù)值10推送到操作數(shù)棧的棧頂中。4190028f6c6b8a0e8a1ab836e687bfc8.webp然后執(zhí)行指令:imul :運(yùn)算指令,對(duì)操作數(shù)棧中的兩個(gè)數(shù)據(jù)進(jìn)行乘法運(yùn)算 那么,此時(shí)虛擬機(jī)棧中的運(yùn)行情況:10和1進(jìn)行乘法運(yùn)算,得到結(jié)果10存入到操作數(shù)棧中fe7d45d8ce348935c06b8d00a532858a.webp繼續(xù)執(zhí)行指令:istore_3 將操作數(shù)棧存儲(chǔ)到局部變量表中,此時(shí)虛擬機(jī)棧的運(yùn)行情況如下:38fd924c137eed7f27f8b985be6ae097.webp繼續(xù)執(zhí)行指令:iload_3 :將局部變量表3的數(shù)據(jù),加載到操作數(shù)棧中ireturn :將操作數(shù)棧中的數(shù)據(jù)取出,然后壓人調(diào)用者的棧幀的操作數(shù)棧中 此時(shí)test()方法執(zhí)行完畢,會(huì)從線程1 的虛擬機(jī)棧中出棧,釋放 那么此時(shí)虛擬機(jī)棧的運(yùn)行情況:此時(shí)虛擬機(jī)棧中,只剩下main棧幀,而test()方法已經(jīng)執(zhí)行完畢了出棧了,需要注意的是main棧幀的操作數(shù)棧中有一個(gè)常量10這個(gè)常量10就是test棧幀執(zhí)行ireturn 指令,壓入到main棧幀的操作數(shù)棧中的。c15f306036e971d365f97216e1b60ad0.webpmain方法執(zhí)行完畢從虛擬機(jī)棧中出棧,OK那么此時(shí),我們的整個(gè)代碼就執(zhí)行結(jié)束了。其實(shí)整個(gè)過程就是虛擬機(jī)棧的執(zhí)行的過程,相信大家都理解了虛擬機(jī)棧的作用以及運(yùn)行過程了,嗯....可以吊打面試官了。

          • 局部變量表:變量和引用變量 :::tips 局部變量表,用于存放局部變量就是方法中的變量,首先它是一個(gè)32位的長度,主要存放Java的八大基礎(chǔ)數(shù)據(jù)類型,如果是64位的就使用高低位占用兩個(gè)也可以存放下,如果是局部的一些對(duì)象,只需要存放它的一個(gè)引用地址即可。默認(rèn)會(huì)有一個(gè)this當(dāng)前類的對(duì)象的引用 :::

          • 操作數(shù)棧

          操作數(shù)棧存放Java方法的操作數(shù)的,它就是一個(gè)棧結(jié)構(gòu)先進(jìn)后出。操作數(shù)棧就是用來操作,操作的元素可以是任意的Java數(shù)據(jù)類型,所以當(dāng)一個(gè)方法剛剛開始的時(shí)候,這個(gè)方法的操作數(shù)棧就是空的。操作數(shù)棧本質(zhì)上是JVM執(zhí)行引擎的一個(gè)工作區(qū),也就是所方法在執(zhí)行,才會(huì)對(duì)操作數(shù)棧進(jìn)行操作,如果代碼不執(zhí)行,操作數(shù)棧其實(shí)就是空的。一般操作系統(tǒng):需要有這些東西CPU + 主內(nèi)存 + 緩存 :::tips JVM是一個(gè)模擬版的操作系統(tǒng),JVM執(zhí)行引擎(CPU) + 棧、堆等(主內(nèi)存) + 操作數(shù)棧(緩存) ::: 指令:都是有執(zhí)行引擎來處理的

          ICONST_0(iconst_<n>)?:?將一個(gè)常量0(n)壓入操作數(shù)棧
          ISTORE 1 (istore_<n>):表示將操作數(shù)棧存入到局部變量表?下標(biāo)為1(n)的位置
          ILOAD?1?:加載存儲(chǔ)指令?將局部變量下標(biāo)為1的值?加載到操作數(shù)棧
          ILOAD?2?:加載存儲(chǔ)指令?將局部變量下標(biāo)為2的值?加載到操作數(shù)棧
          IADD?:?算法指令?兩條數(shù)據(jù)從操作數(shù)棧出棧相加(執(zhí)行引擎進(jìn)行計(jì)算)?,運(yùn)算后的結(jié)果入到操作數(shù)棧(why?)?而執(zhí)行引擎相當(dāng)于CPU不做數(shù)據(jù)的存儲(chǔ),操作數(shù)棧相當(dāng)于緩存,可以存儲(chǔ)中間數(shù)據(jù)
          BIPUSH?10?:?將10常量壓入操作數(shù)棧
          IMUL :?算法指令?乘法。?操作數(shù)棧出棧,執(zhí)行引擎計(jì)算得到的結(jié)果,存入操作數(shù)棧
          ISTORE 3 :操作數(shù)棧出棧,存入到局部變量表下標(biāo)為3的位置
          ILOAD?3?:?將局部變量下標(biāo)為3的值?加載到操作數(shù)棧
          IRETURN :?方法的返回指令:?因?yàn)閳?zhí)行引擎都是處理操作數(shù)棧中的數(shù)據(jù)
          • 動(dòng)態(tài)鏈接

          Java語言的特性多態(tài),具體的會(huì)在后面的章節(jié)中單獨(dú)講解。

          • 完成出口 返回地址

          正常返回(調(diào)用程序計(jì)數(shù)器中的地址作為返回)、異常的話(通過異常處理器表<非棧幀中的>來確定)

          程序計(jì)數(shù)器

          指向當(dāng)前線程正在執(zhí)行的字節(jié)碼的指令地址

          注意 :::tips 程序計(jì)數(shù)器是唯一不會(huì)發(fā)生OOM的內(nèi)存溢出 ::: 其實(shí)在講虛擬機(jī)棧運(yùn)行過程的時(shí)候,圖中有一個(gè)程序計(jì)數(shù)器。程序計(jì)數(shù)器是一塊很小的內(nèi)存空間,主要用來記錄各個(gè)線程執(zhí)行的字節(jié)碼的地址,例如:分支、循環(huán)、跳轉(zhuǎn)、異常、線程恢復(fù)等都依賴于計(jì)數(shù)器。為什么要有程序計(jì)數(shù)器這個(gè)東西呢?JVM中的程序計(jì)數(shù)器 映射了操作系統(tǒng):CPU時(shí)間片輪轉(zhuǎn)機(jī)制。由于Java是多線程語言,當(dāng)執(zhí)行的線程數(shù)量超過CPU核數(shù)時(shí),線程之間會(huì)根據(jù)時(shí)間片輪詢爭奪CPU資源。如果一個(gè)線程的時(shí)間片用完了,或者是其他原因?qū)е逻@個(gè)線程的CPU資源被提前搶奪,那么這個(gè)退出的線程就需要單獨(dú)一個(gè)程序計(jì)數(shù)器,來記錄下一條的運(yùn)行的指令. 如下圖,需要程序計(jì)數(shù)器來記錄運(yùn)行的指令。57a40a944c000829945af9cdf84b5816.webp因?yàn)?JVM 是虛擬機(jī),內(nèi)部有完整的指令與執(zhí)行的一套流程,所以在運(yùn)行 Java 方法的時(shí)候需要使用程序計(jì)數(shù)器(記錄字節(jié)碼執(zhí)行的地址或行號(hào)),如 果是遇到本地方法(native 方法),這個(gè)方法不是 JVM 來具體執(zhí)行,所以程序計(jì)數(shù)器不需要記錄了,這個(gè)是因?yàn)樵诓僮飨到y(tǒng)層面也有一個(gè)程序計(jì)數(shù)器, 這個(gè)會(huì)記錄本地代碼的執(zhí)行的地址,所以在執(zhí)行 native 方法時(shí),JVM 中程序計(jì)數(shù)器的值為空(Undefined)。

          本地方法棧

          執(zhí)行的是native關(guān)鍵字的方法,native的關(guān)鍵字的方法是在C/C++中實(shí)現(xiàn)的,例如hashcode()方法,有一個(gè)動(dòng)態(tài)鏈接hashcode.dll,本地方法并不是Java實(shí)現(xiàn)的。為什么會(huì)有本地方法棧的原因是:虛擬機(jī)規(guī)范中規(guī)定的。(后續(xù)版本中虛擬機(jī)棧和本地方法棧合二為一了) 為什么Java要用native方法呢?歷史的一些原因,Java實(shí)現(xiàn)不了的用c/c++去實(shí)現(xiàn) 本地方法棧和虛擬機(jī)棧是非常相似的一個(gè)區(qū)域,只不過本地方法棧服務(wù)的對(duì)象時(shí)native方法。

          方法區(qū)

          方法區(qū)與堆空間類似,也是一個(gè)共享內(nèi)存的區(qū)域,方法區(qū)是線程共享的,加入兩個(gè)線程都視圖訪問方法區(qū)中的同一個(gè)類信息,而這個(gè)類還沒有裝入JVM,那么此時(shí)就只允許一個(gè)線程加載它,另一個(gè)線程必須等待。在HotSpot虛擬機(jī)、Java7版本中已經(jīng)將永久代的靜態(tài)變量和運(yùn)行時(shí)常量池轉(zhuǎn)移到堆中了,其余部分則存儲(chǔ)在JVM的非堆內(nèi)存中,而Java8版本已經(jīng)將方法區(qū)中實(shí)現(xiàn)的永久代去掉了,并用元空間代替了之前的永久代,并且元空間的存儲(chǔ)位置是本地內(nèi)存。邏輯劃分 JDK1.7 永久代、JDK1.8 元空間

          放class文件,解析,放到常量池 運(yùn)行時(shí)常量池(Runtime Constant Pool)是每一個(gè)類或接口的常量池的運(yùn)行時(shí)表示形式,它包括了若干種不同的常量:從編譯期可知的數(shù)值字面量到必須運(yùn)行期解析后才能獲得的方法或字段引用。運(yùn)行時(shí)常量池是方法區(qū)的一部分。運(yùn)行時(shí)常量池相對(duì)于Class常量池的另外一個(gè)重要特征具備動(dòng)態(tài)性,我會(huì)在后期文章中詳細(xì)講解常量池 運(yùn)行時(shí)常量池:

          • class文件加載到常量池--- 符號(hào)引用替換為直接引用 例如:person類引用了Tool工具類 內(nèi)存地址(直接引用)

          堆是JVM上最大的內(nèi)存區(qū)域,我們申請(qǐng)的幾乎所有的對(duì)象,都是在這里存儲(chǔ)的。包括常說的垃圾回收、操作的對(duì)象就是堆。堆空間一般是程序啟動(dòng)時(shí),就申請(qǐng)了,但是并不一定會(huì)全部使用。堆一般設(shè)置成可伸縮的 隨著對(duì)象的頻繁創(chuàng)建,堆空間占用的越來越多,就需要不定期的對(duì)不再使用的對(duì)象進(jìn)行回收,這個(gè)過程就叫做GC(Garbage Collection) 對(duì)于普通對(duì)象,JVM首先會(huì)在堆上創(chuàng)建對(duì)象,然后在其他使用的地方使用它的引用,例如,把這個(gè)引用保存在虛擬機(jī)棧的局部變量表中。對(duì)于基本數(shù)據(jù)類型來說,有兩種情況:

          • 在方法體內(nèi)聲明的基本數(shù)據(jù)類型的對(duì)象,他就會(huì)在棧上直接分配。
          • 其他情況都是在堆上分配的。

          堆大小參數(shù): -Xms:堆的最小值; -Xmx:堆的最大值; -Xmn:新生代的大小; -XX:NewSize;新生代最小值; -XX:MaxNewSize:新生代最大值;

          直接內(nèi)存

          直接內(nèi)存不屬于運(yùn)行時(shí)數(shù)據(jù)區(qū)域,例如給JVM分配了8G內(nèi)存,虛擬內(nèi)存3G,剩下的5G就是直接內(nèi)存也可以成為堆外內(nèi)存。

          直接內(nèi)存有一種更加科學(xué)的叫法,堆外內(nèi)存。JVM 在運(yùn)行時(shí),會(huì)從操作系統(tǒng)申請(qǐng)大塊的堆內(nèi)存,進(jìn)行數(shù)據(jù)的存儲(chǔ);同時(shí)還有虛擬機(jī)棧、本地方法棧和程序計(jì)數(shù)器,這塊稱之為棧區(qū)。操作系統(tǒng)剩余的 內(nèi)存也就是堆外內(nèi)存。

          它不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是 java 虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域;如果使用了 NIO,這塊區(qū)域會(huì)被頻繁使用,在 java 堆內(nèi)可以用directByteBuffer 對(duì)象直接引用并操作;

          這塊內(nèi)存不受 java 堆大小限制,但受本機(jī)總內(nèi)存的限制,可以通過-XX:MaxDirectMemorySize 來設(shè)置(默認(rèn)與堆內(nèi)存最大值一樣),所以也會(huì)出現(xiàn) OOM 異 常。

          小結(jié): 1、直接內(nèi)存主要是通過 DirectByteBuffer 申請(qǐng)的內(nèi)存,可以使用參數(shù)“MaxDirectMemorySize”來限制它的大小。

          2、其他堆外內(nèi)存,主要是指使用了 Unsafe 或者其他 JNI 手段直接直接申請(qǐng)的內(nèi)存。堆外內(nèi)存的泄漏是非常嚴(yán)重的,它的排查難度高、影響大,甚至?xí)斐芍鳈C(jī)的死亡。后續(xù)章節(jié)會(huì)詳細(xì)講。

          同時(shí),要注意 Oracle 之前計(jì)劃在 Java 9 中去掉 sun.misc.Unsafe API。這里刪除 sun.misc.Unsafe 的原因之一是使 Java 更加安全,并且有替代方案。目前我們主要針對(duì)的 JDK1.8,JDK1.9 暫時(shí)不放入討論范圍中。 EHcache、消息中間件大量的使用直接內(nèi)存。mac 啟動(dòng)HSDBsudo java -cp /Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/sa-jdi.jar sun.jvm.hotspot.HSDB 啟動(dòng)HSDB工具


          瀏覽 70
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  欧美日一道夲 | 蜜乳一区二区三区 | 91亚洲视频 | 久久国产免费 | 97剪辑福利视频 |