JVM是如何分配管理內存的?

      網友投稿 673 2025-04-01

      寫在前面:博主是一只經過實戰開發歷練后投身培訓事業的“小山豬”,昵稱取自動畫片《獅子王》中的“彭彭”,總是以樂觀、積極的心態對待周邊的事物。本人的技術路線從Java全棧工程師一路奔向大數據開發、數據挖掘領域,如今終有小成,愿將昔日所獲與大家交流一二,希望對學習路上的你有所助益。同時,博主也想通過此次嘗試打造一個完善的技術圖書館,任何與文章技術點有關的異常、錯誤、注意事項均會在末尾列出,歡迎大家通過各種方式提供素材。


      對于文章中出現的任何錯誤請大家批評指出,一定及時修改。

      有任何想要討論和學習的問題可聯系我:zhuyc@vip.163.com。

      發布文章的風格因專欄而異,均自成體系,不足之處請大家指正。

      JVM是如何分配管理內存的?

      本文關鍵字:JVM、虛擬機棧、Java堆、方法區、運行時常量池

      文章目錄

      JVM是如何分配管理內存的?

      一、JVM內存區域

      1. PC寄存器

      2. Java虛擬機棧

      3. 本地方法棧

      4. Java堆

      5. 方法區

      6. 運行時常量池

      二、常見結構存儲位置

      1. 普通成員變量

      2. 靜態成員變量與靜態代碼塊

      3. 構造方法和動態代碼塊

      4. 普通方法與靜態方法

      5. 方法局部變量

      一、JVM內存區域

      Java程序在運行時,首先要讀取編譯后的class文件,由于我們在編寫源碼時會定義和使用各種結構和對象,那么在進行加載時,JVM會將分配得到的內存劃分為多個區域。通常我們會粗淺地將內存劃分為“棧”和"堆"兩個區域,但是對于Java虛擬機來說我們應該進一步剖析。

      由JVM創建的不同區域,有些會隨著虛擬機啟動而創建,隨著虛擬機退出而銷毀,如:方法區(Method)、Java堆。還有一些是與線程一一對應的,會隨著線程開始和結束而被創建和銷毀,如:PC寄存器、Java虛擬機棧、本地方法棧。

      1. PC寄存器

      雖然Java虛擬機支持多線程同時執行,但是在任意時刻,一條JVM線程只會執行一個方法的代碼,這個正在被線程執行的方法稱為該線程的當前方法,對應的Java虛擬機棧被稱為當前棧幀。

      PC寄存器是一塊較小的內存空間,可以看作是當前線程所執行的字節碼的行號指示器,每一條JVM線程都有自己的PC寄存器。字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,它是程序控制流的指示器,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個寄存器來完成。

      如果當前方法不是native的,那PC寄存器就保存Java虛擬機正在執行的字節碼指令的地址,如果該方法是native的,則PC寄存器的值是undefined。(關于native的說明見:3.本地方法棧)

      2. Java虛擬機棧

      每一條JVM線程都有自己私有的Java虛擬機棧,與線程同時創建,用于存儲棧幀,其中包含局部變量和一些尚未算好的結果。另外,需要注意的是:棧(Stack)、Heap(堆)、Java虛擬機棧(Java VM Stack)、Java堆(Java Heap)的概念是不同的,Java虛擬機本身也是一個由其他語言編寫運行的軟件,所以本文只討論JVM所管理的內存區域,并不探討各區域在堆棧中的分布。

      Java虛擬機規范既允許Java虛擬機棧被實現為固定大小,也允許根據計算動態來擴展和收縮。Java虛擬機棧描述的是Java方法執行的線程的內存模型:每個方法被執行的時候,Java虛擬機都會創建一個棧幀,用于存儲局部變量表、操作數棧、動態鏈接等信息,每一個方法從被調用,到執行完畢的過程就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。

      當調用新的方法時,新的棧幀會隨之創建,程序的控制權也會進行移交給調用的新方法,成為新的當前棧幀。在方法執行完畢進行返回時,當前棧幀會傳回此方法的執行結果給前一個棧幀(調用這個新方法的棧幀),然后虛擬機就會丟棄當前棧幀,前一個棧幀成為當前棧幀,大家可以用這篇文章來理解一下這個過程:Java方法的嵌套與遞歸調用。

      局部變量表

      每個棧幀內部都包含一組被稱為局部變量表的變量列表,長度在編譯期時被確定。一個局部變量可以保存一個類型為boolean、byte、char、short、int、float、reference或returnAddress的數據。兩個局部變量可以保存一個類型為long或者double的數據。

      局部變量使用索引來進行定位訪問,首個局部變量的索引值為0,最大值小于局部變量表的長度。對于long和double,由于占用了兩個連續的局部變量,則采用局部變量中較小的索引值來定位。

      操作數棧

      每個棧幀內部都包含一個被稱為操作數棧的后進先出棧,操作數棧的最大深度在編譯器被確定,一般的操作數棧指的就是“當前棧幀的操作數棧”。

      在棧幀剛剛創建時,操作數棧是空的。JVM提供一些字節碼指令來從局部變量表或對象實例的字段中復制常量或變量的值到操作數棧中,也提供了一些指令用于從操作數棧取走數據、操作數據以及把操作結果重新入棧。在調用方法時,操作數棧也用來準備調用方法的參數以及接收方法返回結果。

      在任意時刻,操作數棧都會有一個確定的棧深度,一個long或者double類型的數據會占用兩個單位的棧深度,其他數據類型會占用一個單位的棧深度。

      動態鏈接

      每個棧幀內部都包含一個指向當前方法所在類型的運行時常量池的引用,來對當前方法的代碼實現動態鏈接。在class文件里面,一個方法如果要調用其他方法,或者訪問成員變量,需要通過符號引用來表示,動態鏈接的作用就是將這些符號引用所表示的方法轉換為對實際方法的直接引用。

      3. 本地方法棧

      由于有時可能需要調用其他語言(如C語言)所編寫的方法,就需要使用到傳統的棧(C stack)來支持native方法的執行。native在Java語言中是一個修飾符,如果一個方法被native修飾,那么就代表這個方法是一個java調用非java代碼的接口。在定義一個native method時,不需要指定方法體,與聲明接口中的方法類似,具體的方法實現會在dll或其他庫文件中,在運行時需要一并加載。

      本地方法棧與Java虛擬機棧的作用十分相似,區別就在于Java虛擬機棧為虛擬機執行Java方法服務,而本地方法棧則是為虛擬機調用的本地方法服務。

      4. Java堆

      Java堆是JVM所管理的內存中最大的一塊區域,并且是被所有線程共享的一塊內存區域,在虛擬機啟動時被創建。Java堆中主要存儲的就是對象的實例,包括數組類型的實例。

      Java堆中所存儲的對象由自動內存管理系統,也就是垃圾收集器進行管理,不需要手動進行銷毀和釋放。另外,Java堆所對應的區域不需要連續。

      5. 方法區

      方法區與Java堆一樣,是一塊各個線程共享的內存區域,用于存儲已被虛擬機加載的類的結構信息,包括運行時常量池、構造函數和普通方法、靜態變量等數據。

      方法區在虛擬機啟動的時候被創建,雖然邏輯上方法區屬于堆的一部分,但是也可以選擇在這個區域不實現垃圾收集與壓縮。

      原文引述《Java虛擬機規范(Java SE 8版)》中的內容:

      這個版本的Java虛擬機規范也不限定實現方法區的內存位置和編譯代碼的管理策略。方法區的容量可以是固定的,也可以隨著程序執行的需求動態擴展,并在不需要過多空間時自動收縮。方法區在實際內存空間中可以是不連續的。

      原文引述《深入理解Java虛擬機:JVM高級特性與最佳實踐(第3版)》中的內容:

      在JDK 6的時候HotSpot開發團隊就有放棄永久代,逐步改為采用本地內存(Native Memory)來實現方法區的計劃了,到了JDK 7的HotSpot,已經把原本放在永久代的字符串常量池、靜態變量等移出,而到了JDK 8,終于完全廢棄了永久代的概念,改用與JRockit、J9一樣在本地內存中實現的元空間(Metaspace)來代替,把JDK 7中永久代還剩余的內容(主要是類型信息)全部移到元空間中。

      引述這兩段話的原因在于不少初學者都在糾結很多類中定義的結構到底存儲在什么位置的問題,筆者在這里幫助大家再次明確一下:

      不同版本的JVM有對方法區的管理方式并不相同

      有多種Java虛擬機都可以運行Java程序,對各區域的管理也有差別

      HotSpot VM與JRockit VM已在Oracle JDK8版本完成合并

      所以當我們在進行探討時一定要明確具體的虛擬機和JDK版本,方法區本身是有JVM分配管理的區域之一,從上面的敘述中我們已經知道,對于Oracle JDK8版本,已經不再使用永久代來實現方法區,方法區中的內容全部移動存儲至本地內存的元空間中。

      6. 運行時常量池

      JVM是如何分配管理內存的?

      首先強調:運行時常量池并不等同于常量池! 運行時常量池是方法區的一部分,是class文件中每一個類或接口的常量池表的運行時表示,包括了若干種不同的常量:在編譯期可知的數值字面量以及在運行期解析后才能獲得的方法或字段引用。

      在Java虛擬機加載類和接口后,就會創建對應的運行時常量池。Java虛擬機為每個類型都維護著一個常量池,是Java虛擬機中的運行時數據結構。運行時常量池中的所有引用最初都是符號引用,對于不同的結構(類、接口、數組等)的符號引用,也會有相應的規范和格式。

      由于運行時常量池的符號引用較為復雜,同時對常量池的解釋涉及到類的加載機制,所以在本文中不再贅述,后續將在其他文章中說明(包括字符串常量的規定策略)。

      二、常見結構存儲位置

      在閱讀了前面的內容后,我們可以記一波結論了,了解一些底層的東西有利于我們去解釋或記憶某些用法和特點。

      1. 普通成員變量

      普通的成員變量由于是創建對象后才能使用的,所以基本數據類型的值或引用(與成員變量類型無關)都存放在對應的實例空間,在Java堆中。

      2. 靜態成員變量與靜態代碼塊

      靜態成員變量與靜態代碼塊都是直接在類下使用static聲明的結構,存儲在方法區中。

      3. 構造方法和動態代碼塊

      構造方法也是類似于方法的一種結構,被new調用時才會執行,而動態代碼塊被編譯后會出現在構造函數中,它們都存儲在方法區中。

      編譯前

      public class Person{ { System.out.println("init"); } public Person(){ System.out.println("default"); } public Person(int a){ System.out.println("another"); } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      編譯后

      4. 普通方法與靜態方法

      普通方法和靜態方法雖然在調用和使用時有所區別,但本質上都是方法結構,對于一個類來說只要加載一次就可以了,也存放在方法區中。

      5. 方法局部變量

      在方法中定義的變量,由于有局部變量表的存在,基本數據類型直接存放在JVM棧中,對于引用類型的變量,在JVM棧中只存放引用(reference),而對應的實例存放在Java堆中。

      Java JVM

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

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

      上一篇:榮威RX5車主購車后,首年贈送的WIFI流(榮威rx5網絡流量免費嗎)
      下一篇:excel表格如何把兩列數據合并
      相關文章
      亚洲精品在线不卡| 亚洲欧洲春色校园另类小说| 国产精品亚洲自在线播放页码| 亚洲AV无码乱码在线观看富二代 | 亚洲美女视频一区| 亚洲尹人香蕉网在线视颅| 久久国产精品亚洲综合| 午夜亚洲国产理论秋霞| 久久久久久a亚洲欧洲AV| 亚洲高清国产AV拍精品青青草原| 亚洲精品国精品久久99热一| 亚洲级αV无码毛片久久精品| 亚洲人成色777777在线观看| 人人狠狠综合久久亚洲88| 亚洲精品国产品国语在线| 久久国产精品亚洲一区二区| 久久久婷婷五月亚洲97号色| 亚洲高清视频免费| 亚洲国产精品久久网午夜| 色偷偷女男人的天堂亚洲网| 亚洲综合无码无在线观看| 亚洲第一综合天堂另类专| 极品色天使在线婷婷天堂亚洲| 一本久久综合亚洲鲁鲁五月天 | 亚洲男女一区二区三区| 亚洲一区二区三区无码国产| 性xxxx黑人与亚洲| 国产精品亚洲专区无码牛牛| 亚洲国产午夜中文字幕精品黄网站| 亚洲免费视频一区二区三区| 亚洲色欲一区二区三区在线观看| 亚洲av无码一区二区三区不卡| 亚洲视频一区调教| 亚洲一区在线视频| 亚洲AV无码成人精品区日韩| 亚洲精品456播放| 亚洲爆乳精品无码一区二区三区| 久久亚洲私人国产精品| 四虎必出精品亚洲高清| 国产偷国产偷亚洲高清在线| 中文字幕亚洲天堂|