【云駐共創】JVM內存模型的探知之旅

      網友投稿 630 2025-04-03

      1. JVM介紹

      1.1 什么是JVM?

      JVM是Java Virtual Machine(Java虛擬機)的簡稱,是一種用于計算設備的規范,是一個虛構出來的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。

      1.2 JVM的優點

      1.2.1 一次編寫,到處運行。

      JVM可以讓java程序,一次編寫,導出運行。讓底層代碼和運行環境分離開,編寫好一份代碼后,不用再次修改內容,只用通過安裝不同的JVM環境自動進行轉換即可運行,在各種系統中無縫連接。

      1.2.2 自動內存管理,垃圾回收機制。

      在Java誕生之時,C和C++稱霸天下,但是這兩種語言中沒有內存管理機制,是通過手動操作來進行的管理,非常麻煩和繁瑣。

      此時Java應運而生,為了處理內存管理這個方面,專門設計了垃圾回收機制,來自動進行內存的管理。極大的優化了操作,讓程序員們不用正在噼里啪啦在碼代的海洋中遨游時,還要操心內存會不會溢出這些“影響我方輸出”的問題,頓時獲得了成噸的好評。

      1.2.3 數組下標越界檢查

      在Java誕生之時,還有個讓當時C和C++大佬頭疼的問題是,數組下標越界是沒有檢查機制的,這還了得,又是一個影響“我方暴力輸出”的罪魁禍首,因此JVM繼續抱著暖男的思想,又來了個愛的抱抱。

      JVM又一次看見了大佬們的煩惱,果斷提供了數組下標越界的自動檢查機制,在檢測到數組下標出現越界后,會在運行時自動拋出“java.lang.ArrayIndexOutOfBoundsException”這個異常,在當時可是感動了很多業界大佬(我猜的)。

      1.2.4 多態

      JVM還有一個多態功能,是通過相同接口,不同的實例進行實現,完成不同的業務操作,比如:定義了一個動物接口(里面有一個吃的方法),我們就可以通過這個動物創造小貓(吃魚),再創造一個狗狗(吃肉),再創造一個小助手(吃零食,O(∩_∩)O哈哈~)。

      仔細想想,對我們有啥影響呢,那好處老多了,比如:

      (1)消除類型之間的耦合關系;

      (2)可替換性;

      (3)可擴充性;

      (4)接口性;

      (5)靈活性;

      (6)簡化性;

      1.3 JVM、JRE、JDK之間的關系

      1.3.1 JVM的簡介

      JVM是Java Virtual Machine的簡稱,是Java虛擬機,是一種模擬出來的虛擬計算機,它通過在不同的計算機環境當中模擬實現計算功能來實現的。

      引入Java虛擬機后,Java語言在不同平臺上運行時就不需要重新編譯。在其中,Java虛擬機屏蔽了與具體平臺的相關信息,使得Java源程序在編譯完成之后即可在不同的平臺運行,達到“一次編譯,到處運行”的目的,Java語言重要的特點之一跨平臺,也即與平臺的無關性,其關鍵點就是JVM。

      1.3.2 JRE的簡介

      JRE是Java Runtime Environment的簡稱,是Java運行環境,是讓操作系統運行Java應用程序的環境,其內部包含JVM,也就是說JRE只負責對已經存在的Java源程序進行運行的操作,它不包含開發工具JDK,對JDK內部的編譯器、調試器和其它工具均不包含。

      1.3.3 JDK的簡介

      JDK是Java Development Kit的簡稱,是Java開發工具包,是整個Java程序開發的核心。其主要包含了JRE、Java的系統類庫以及對Java程序進行編譯以及運行的工具,例如:javac.exe和java.exe命令工具等。

      1.4 JVM的常見實現

      Oracle(Hotspot、Jrockit)、BEA(LiquidVM)、IBM(J9)、taobaoVM(淘寶專用,對Hotspot進行了深度定制)、zing(垃圾回收機制非常快,到達1毫秒左右)。

      1.5 JVM的內存結構圖

      當Java程序編譯完成為.class文件==》類加載器(ClassLoader)==》將字節碼文件加載進JVM中;

      1.5.1方法區、堆

      方法區中保存的主要是類的信息(類的屬性、成員變量、構造函數等)、堆(創建的對象)。

      1.5.2虛擬機棧、程序計數器、本地方法棧

      堆中的對象調用方法時,方法會運行在虛擬機棧、程序計數器、本地方法棧中。

      1.5.3執行引擎

      執行方法中代碼時,代碼通過執行引擎執行中的“解釋器”執行;方法中經常調用的代碼,即熱點代碼,通過“即時編譯器”執行,其執行速度非常快。

      1.5.4 GC(垃圾回收機制)

      GC是針對堆內存中沒有引用的對象進行回收,可以手動也可以自動。

      1.5.5本地方法接口

      因為JVM不能直接調用操作系統的功能,只能通過本地方法接口來調用操作系統的功能。

      2. JVM內存結構-程序計數器

      2.1 程序計數器的定義

      Program Counter Register即程序計數器(寄存器),用于記錄下一條Jvm指令的執行地址。

      2.2 操作步驟

      javap主要用于操作JVM,javap -c 是對java代碼進行反匯編操作。下圖為通過先編譯demo.java后,再執行javap -c demo的輸出結果:

      其中第一列為二進制字節碼,即JVM指令,第二列為java源代碼。第一列中的序號為JVM指令的執行地址。

      JVM會通過程序計數器記錄下一條需要執行的JVM指令的地址(比如第一行的0),然后交給解釋器解析為機器碼,最后交給cpu(只能識別機器碼),完成一行的執行。

      想要執行下一行,繼續讓JVM的程序計數器記錄下一條地址(比如第二行的3),再交給解釋器解析后給cpu,以此類推執行結束。

      2.3 特點

      2.3.1 線程私有的

      2.3.2 不會存在內存溢出

      3. JVM內存結構-虛擬機棧

      3.1 定義

      虛擬機棧是每個線程運行所需要的內存空間,每個棧中由多個棧幀組成,每個線程中只能有一個活動棧幀(對應當前正在執行的方法),所有棧幀都遵循后進先出,先進后出的原則。

      棧幀是每次調用方法時所占用的內存,在棧幀中保存的內容參數、局部變量、返回地址。

      注1:垃圾回收不涉及棧內存,因為棧內存是由方法調用產生的,當方法調用結束后會彈出棧。

      注2:棧內存不是分配的越大越好,因為物理內存是一定的,棧內存越大,可以支持更多的遞歸調用,但是可執行的線程數會越來越少。

      注3:方法的局部變量,當其沒有逃離方法的作用范圍時,是線程安全的;如果其引用了對象(比如靜態變量,即共享變量,用對象作為參數的方法,返回值為對象的方法),并且逃離出了方法的作用范圍,就需要考慮線程安全的問題了。

      3.2 棧內存溢出

      3.2.1 發生原因

      【云駐共創】JVM內存模型的探知之旅

      (1)虛擬機棧中,棧幀過多(無限遞歸),如圖1棧幀過多;

      (2)每個棧幀所占用過大,如圖2 棧幀過大。

      圖1 棧幀過多? ? ? ? ? ? ? ? ? ? ? ?圖2 棧幀過大

      3.2.2 棧內存溢出小實驗

      3.2.2.1 棧幀過多的小實驗

      無限遞歸調用(棧幀過多)的小實驗,method1()方法在主方法中無限調用自己,那么會發生什么情況呢?

      答案很明顯,程序崩潰了,產生了棧內存溢出錯誤,如下圖所示:

      -Xss:該參數規定了每個線程虛擬機棧的大小;

      接著我們通過設置一個虛擬機棧的大小是256k試試會發生什么?

      我們發現當我們調整了虛擬機棧的大小后執行了4315次方法后內存就溢出了,而調整虛擬機棧之前,我們是23268次,很明顯我們可以通過-Xss參數調整虛擬機棧的大小來控制內存的溢出情況。

      3.2.2.2 線程運行診斷小實驗

      想象中的場景,大佬在瘋狂輸出,突然CPU爆表了,顯示CPU占用過多,如何去定位哪行代碼的問題,是的是哪行(大佬都很忙的好嗎,當然要精確了,一分鐘幾千萬上下的,O(∩_∩)O哈哈~)?

      Linux環境下:

      在后臺運行Stack_6這個java字節碼(.class)文件:

      注:無論是否將nohup命令的輸出重定向到終端,輸出都將附加到當前目錄的 nohup.out 文件中。

      (1)通過top命令,查看進程(相當于任務管理器),發現了一個占用CPU達到100%的可疑家伙,這還了得,趕緊瞅瞅具體發生了什么,還有沒有王法,這讓其他小伙伴還怎么愉快的玩耍,秒速糾錯ING。。。

      注:top命令,查看哪個進程占用CPU過高,返回進程號。

      (2) 通過ps H -eo pid,tid ,%cpu | grep 命令過濾任務管理器中的內容。

      注:ps H -eo pid,tid,%cpu |grep,是通過ps命令查看哪個線程占用CPU過高,返回進程id,其中pid為進程id,tid為線程id,%cpu為CPU占用情況;

      發現了罪魁禍首,這一串串心驚肉跳的紅色。。。

      (3) 通過jstack 進程id查看,20389這個有問題的進程中具體的情況。

      注:jstack 進程id,是通過jstack 命令定位具體哪段代碼出現占用CPU過高,注意jstack命令查找的線程id是16進制的,需要轉換;

      發現里面有一堆執行的代碼,那么我們怎么找到具體是哪個家伙搞事情的呢?上圖我們可以發現搞事情的線程是20441,那么我們通過計算器將20441轉換為16進制的4FD9再去試試,真相只有一個。

      通過對比nid(線程id)4fd9,我們發現這個叫thread1的線程一直在運行(RUNNABLE狀態),并且查看到位置是位于Stack_6.java文件的第11行出現的問題。。。

      現在我們回到源碼中,在Stack_6文件的第11行,我們發現原來這里一直在執行死循環,終于找到你,還好我沒放棄,奈斯~

      4. JVM內存結構-本地方法棧

      4.1 定義

      由于Java本身有時候是無法直接和操作系統底層交互的,但有時候需要Java調用本地的C或C++方法,所以此時本地方法棧應運而生,它們是一類帶有native關鍵字的方法。

      5. JVM內存結構-堆

      5.1 定義

      Heap堆:是通過new關鍵字創建的對象存放在堆中

      5.2 特點

      5.2.1線程共享

      堆中存放的對象都是線程共享的,因此都是需要考慮線程安全問題的。

      5.2.2有垃圾回收機制

      因為堆中存放的對象存放了大量的對象,因此給他配了個小助手——垃圾回收機制(可以調自動擋和手動擋哦~)。

      5.3 堆內存溢出小實驗

      5.3.1 修改堆內存大小參數的小實驗

      繼續幻想一個場景,當一個大佬開發完一個段代碼的時候(當然一般大佬都是很自信的,我寫的代碼怎么可能有問題,不存在的。。。),但是測試可跑不了,穩妥起見咱們還是默默得搞測試試試嘛,安全第一。但是機器的內存就這么大,大佬肯定跑了很多次了,都沒出現問題的,這不是找茬嘛。。。還是默默改下機子參數再試試吧(想去懟大佬,一定要拿出證據嘛~)。

      -Xmx:JVM調優參數之一,該參數表示java堆可以擴展到的最大值。下面上案例:

      在執行了26次之后,果斷的后臺報了堆內存溢出錯誤。

      下面通過-Xmx JVM調優參數將堆內存調小至8m,再試試會發生什么呢?

      操作基本和棧內存溢出的時候的案例一樣,次數明顯變小了,只調用了17次就出現了堆內存溢出錯誤了。

      5.3.2 堆內存診斷的小實驗

      jps工具:查看當前系統中有哪些java進程

      jmap工具:查看堆內存的占用情況jmap -heap 進程id

      jconsole工具:圖形化的工具,擁有多功能的監測功能,可以連續監測。

      下面我們通過運行代碼后通過jconsole可視化圖形工具,來查看堆內存的使用情況。

      上圖我們可以看到,在我們創建10mb的數組對象時,內存使用有一定上升;然后在我們手動調用垃圾回收機制后,內存又得到了很大的釋放。

      6. JVM內存結構-方法區

      6.1 定義

      Java虛擬機中有一個被所有jvm線程共享的方法區。方法區有點類似于傳統編程語言中的編譯代碼塊或者操作系統層面的代碼段。它存儲著每個類的構造信息,譬如運行時的常量池,字段,方法數據,以及方法和構造方法的代碼,包括一些在類和實例初始化和接口初始化時候使用的特殊方法。

      方法區有個別稱non-heap(非堆),可以看作是一塊獨立于堆的內存空間,是JVM規范中定義的一個概念,用于存儲類信息、常量池、靜態變量,JIT編譯后的代碼等數據,具體放在哪里,不同的實現可以放在不同的地方。

      6.2 特點

      (1)方法區與java堆一樣,是各個線程共享的內存區域;

      (2)方法區在JVM啟動的時候被創建;

      (3)方法區的大小,跟堆空間一樣,可以選擇固定大小或擴展;

      (4)方法區的大小決定了系統可以保存多少個類,如果系統定義了太多的類,導致方法區溢出,虛擬機同樣會拋出溢出錯誤OutOfMemoryError;

      (5)關閉JVM就會釋放這個區域的內存。

      6.3 JVM內存結構示意圖

      在JVM內存結構1.6的時候,方法區保存在內存結構中,叫做永久代,里面存儲了運行時的常量池(包含串池StringTable)、類的信息、類加載器;

      在JVM內存結構1.8的時候,方法區做為一個概念,保存在本地內存中,叫做元空間,里面存儲了運行時的常量池、類的信息、類加載器,此時串池(StringTable)儲存在堆之中。

      Java JVM

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:修改的文章只能存在云檔?怎么另存在本地文檔?求教!(我的世界云存檔怎么變成本地存檔)
      下一篇:制作簡歷添加下一頁總是出現在前面,是怎么回事(在簡歷后面再加一頁)
      相關文章
      亚洲乱码中文论理电影| 亚洲网址在线观看你懂的| 亚洲成人高清在线观看| 国产精品亚洲片在线| 国产偷窥女洗浴在线观看亚洲| 色偷偷亚洲男人天堂| 日韩欧美亚洲国产精品字幕久久久 | 亚洲男人都懂得羞羞网站| 亚洲国产精品嫩草影院在线观看 | 亚洲一欧洲中文字幕在线| 国产精品日本亚洲777| 亚洲av日韩av综合| 91亚洲国产成人久久精品网址| 亚洲韩国—中文字幕| 亚洲欧洲日产国产综合网| 婷婷亚洲久悠悠色悠在线播放| 日韩亚洲欧洲在线com91tv| 精品国产_亚洲人成在线高清| 亚洲理论电影在线观看| 国产亚洲av片在线观看播放| 国产亚洲精品一品区99热| 亚洲av中文无码乱人伦在线r▽| 国产亚洲精品a在线观看app| 亚洲AV无一区二区三区久久| 亚洲国产精品一区| 91亚洲一区二区在线观看不卡| 亚洲专区在线视频| 亚洲国产精品成人综合久久久| 亚洲av无码一区二区三区天堂古代| 亚洲一级毛片免观看| 亚洲偷自拍另类图片二区| 亚洲老熟女五十路老熟女bbw| 亚洲a∨国产av综合av下载| 亚洲成a人片在线观看久| 国产亚洲欧洲Aⅴ综合一区| 亚洲精品高清国产一线久久| 亚洲av不卡一区二区三区| 亚洲成年人电影在线观看| 久久亚洲精品国产亚洲老地址 | 亚洲性在线看高清h片| 亚洲人成网7777777国产|