聊聊JVM記憶體模型
阿新 • • 發佈:2019-12-31
一、前言
轉眼間也過完了最後一個暑假,最近忙於校招,一直在複習以前學過的一些基礎知識,今天就順便總結一下最近複習的JVM相關的知識。
二、JVM記憶體結構
JVM的總體記憶體結構如下圖所示:
大致分為下面幾個重點的內容,本篇文章我們主要分析執行時資料區的幾個結構
- 類裝載器(ClassLoader)(用來裝載.class檔案)
- 執行引擎(執行位元組碼,或者執行本地方法)
- 執行時資料區(方法區、Java堆、Java棧、程式計數器、本地方法棧)
2.1 程式計數器
程式計數器是執行緒私有的區域,每個執行緒當然得有個計數器記錄當前執行到哪個指令。佔用的記憶體空間小,可以把它看成是當前執行緒所執行的位元組碼的行號指示器。如果執行緒在執行Java方法,這個計數器記錄的是正在執行的虛擬機器器位元組碼指令地址;如果執行的是Native方法,這個計數器的值為空(Undefined)。此記憶體區域是唯一一個在Java虛擬機器器規範中沒有規定任何OutOfMemoryError情況的區域。
每條執行緒都需要有一個獨立的程式計數器,各個執行緒之間計數器互不影響,獨立儲存,我們稱之為執行緒私有的區域。
2.2 Java堆
對於大多數應用來說,Java堆是Java虛擬機器器所管理的記憶體中最大的一塊。Java堆是被所有執行緒共享的一塊記憶體區域,在虛擬機器器啟動時建立。該區域的唯一目的就是存放物件例項,幾乎所有的物件例項都在這裡分配記憶體。該區域也是垃圾收集器進行垃圾回收的主要區域。堆的記憶體空間既可以固定大小,也可以在執行時動態地調整,通過如下引數設定初始值和最大值,比如 -Xms256M -Xmx1024M,其中 -X表示它是JVM執行引數,ms是memorystart的簡稱,mx是memory max的簡稱,分別代表最小堆容量和最大堆容量。
可以通過 -Xms 和 -Xmx 兩個虛擬機器器引數來指定一個程式的堆記憶體大小,第一個引數設定初始值,第二個引數設定最大值。
java -Xms1M -Xmx2M HackTheJava複製程式碼
它是JVM用來儲存物件例項以及陣列值的區域,可以認為Java中所有通過new建立的物件的記憶體都在此分配,堆中的物件的記憶體需要等待GC進行回收。
Java堆是垃圾收集器管理的主要區域。由於現在的收集器基本上採用的都是分代收集演演算法,其主要的思想是針對不同型別的物件採取不同的垃圾回收演演算法,可以將堆分成兩塊:
- 新生代(Young Generation)
- 老年代(Old Generation)
堆不需要連續記憶體,並且可以動態增加其記憶體,增加失敗會丟擲 OutOfMemoryError 異常。
(1) 堆是JVM中所有執行緒共享的,因此在其上進行物件記憶體的分配均需要進行加鎖,這也導致了new物件的開銷是比較大的 (2) 所有新建立的Object 都將會儲存在新生代Yong Generation中。如果Young Generation的資料在一次或多次GC後存活下來,那麼將被轉移到老年代Old Generation。新的Object總是建立在Eden Space。
2.3 Java虛擬機器器棧
可以通過 -Xss 這個虛擬機器器引數來指定每個執行緒的 Java 虛擬機器器棧記憶體大小:
java -Xss512M HackTheJava複製程式碼
區域性變量表中存放了編譯期可知的各種:
- 基本資料型別(boolen、byte、char、short、int、 float、 long、double)
- 物件引用(reference型別,它不等同於物件本身,可能是一個指向物件起始地址的指標,也可能是指向一個代表物件的控制程式碼或其他與此物件相關的位置)
- returnAddress型別(指向了一條位元組碼指令的地址)
2.4 本地方法棧
本地方法棧也是一個執行緒私有的區域,本地方法棧與Java虛擬機器器棧的作用相似,兩者的區別在於虛擬機器器棧為虛擬機器器執行Java方法服務,而本地方法棧為虛擬機器器用到的Native方法服務,此區域用於儲存每個Native方法呼叫的狀態。與虛擬機器器棧一樣,本地方法棧也會丟擲StackOverflowError(棧溢位)和OutOfMemoryError(記憶體不足)異常。
2.5 方法區
方法區是各個執行緒共享的記憶體區域,方法區用於儲存已被虛擬機器器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。和堆一樣不需要連續的記憶體,並且可以動態擴充套件,動態擴充套件失敗一樣會丟擲 OutOfMemoryError 異常。
對這塊區域進行垃圾回收的主要目標是對常量池的回收和對類的解除安裝,但是一般比較難實現。HotSpot 虛擬機器器把它當成永久代來進行垃圾回收。但是很難確定永久代的大小,因為它受到很多因素影響,並且每次 Full GC 之後永久代的大小都會改變,所以經常會丟擲 OutOfMemoryError 異常。為了更容易管理方法區,從 JDK 1.8 開始,移除永久代,並把方法區移至元空間,它位於本地記憶體中,而不是虛擬機器器記憶體中。
(1)在Sun JDK中這塊區域對應的為PermanetGeneration,又稱為持久代。 (2)方法區域存放了所載入的類的資訊(名稱、修飾符等)、類中的靜態變數、類中定義為final型別的常量、類中的Field資訊、類中的方法資訊,當開發人員在程式中通過Class物件中的getName、isInterface等方法來獲取資訊時,這些資料都來源於方法區域,同時方法區域也是全域性共享的,在一定的條件下它也會被GC,當方法區域需要使用的記憶體超過其允許的大小時,會丟擲OutOfMemory的錯誤資訊。
三、常用引數
一些jvm中常見的引數配置如下:- -Xms64m 最小堆記憶體 64m.
- -Xmx128m 最大堆記憶體 128m.
- -XX:NewSize=30m 新生代初始化大小為30m.
- -XX:MaxNewSize=40m 新生代最大大小為40m.
- -Xss=256k 執行緒棧大小。
- -XX:+PrintHeapAtGC 當發生 GC 時列印記憶體佈局。
- -XX:+HeapDumpOnOutOfMemoryError 傳送記憶體溢位時 dump 記憶體。
預設的,新生代中各個區域的大小比例為Edem : from : to = 8 : 1 : 1 (可以通過引數 –XX:SurvivorRatio 來設定)。
四、總結
本篇介紹了JVM虛擬機器器中執行時資料區的五個記憶體區域,這些地方也是我們平時開發中最常接觸到的地方,所以對其有所掌握瞭解還是很有必要的,也有助於我們在服務出現告警時進行相關的問題排查。
有任何問題還請各位指正,互相討論學習。
部分參考自:《深入理解Java虛擬機器器》