天天看點

玩轉JVM虛拟機:JVM記憶體結構

玩轉JVM虛拟機:JVM記憶體結構

視訊請在wifi下觀看(土豪随意~),視訊包含内容整理如下:

一,JVM記憶體結構

玩轉JVM虛拟機:JVM記憶體結構

(圖檔來自網絡)

1. 程式計數器

程式計數器(Program Counter Register),也有稱作為PC寄存器。在彙編語言中,程式計數器是指CPU中的寄存器,它儲存的是程式目前執行的指令的位址,當CPU需要執行指令時,需要從程式計數器中得到目前需要執行的指令所在存儲單元的位址,然後根據得到的位址擷取到指令,在得到指令之後,程式計數器便自動加1或者根據轉移指針得到下一條指令的位址,如此循環,直至執行完所有的指令。

雖然JVM中的程式計數器并不像彙編語言中的程式計數器一樣是實體概念上的CPU寄存器,但是JVM中的程式計數器的功能跟彙編語言中的程式計數器的功能在邏輯上是等同的,也就是說是用來訓示執行哪條指令的。

由于在JVM中,多線程是通過線程輪流切換來獲得CPU執行時間的,是以,在任一具體時刻,一個CPU的核心隻會執行一條線程中的指令,是以,為了能夠使得每個線程都線上程切換後能夠恢複在切換之前的程式執行位置,每個線程都需要有自己獨立的程式計數器,并且不能互相被幹擾,否則就會影響到程式的正常執行次序。是以,可以這麼說,程式計數器是每個線程所私有的。

在JVM規範中規定,如果線程執行的是非native方法,則程式計數器中儲存的是目前需要執行的指令的位址;如果線程執行的是native方法,則程式計數器中的值是undefined。

由于程式計數器中存儲的資料所占空間的大小不會随程式的執行而發生改變,是以,對于程式計數器是不會發生記憶體溢出現象(OutOfMemory)的。

2. Java棧

Java棧也稱作虛拟機棧(Java Vitual Machine Stack),也就是我們常常所說的棧,跟C語言的資料段中的棧類似。事實上,Java棧是Java方法執行的記憶體模型。

Java棧中存放的是一個個的棧幀,每個棧幀對應一個被調用的方法,在棧幀中包括局部變量表(Local Variables)、操作數棧(Operand Stack)、指向目前方法所屬的類的運作時常量池的引用(Reference to runtime constant pool)、方法傳回位址(ReturnAddress)和一些額外的附加資訊。當線程執行一個方法時,就會随之建立一個對應的棧幀,并将建立的棧幀壓棧。當方法執行完畢之後,便會将棧幀出棧。是以可知,線程目前執行的方法所對應的棧幀必定位于Java棧的頂部。講到這裡,大家就應該會明白為什麼 在 使用 遞歸方法的時候容易導緻棧記憶體溢出的現象了,以及為什麼棧區的空間不用程式員去管理了,這部分空間的配置設定和釋放都是由系統自動實施的。

局部變量表,顧名思義,想必不用解釋大家應該明白它的作用了吧。就是用來存儲方法中的局部變量(包括在方法中聲明的非靜态變量以及函數形參)。對于基本資料類型的變量,則直接存儲它的值,對于引用類型的變量,則存的是指向對象的引用。局部變量表的大小在編譯器就可以确定其大小了,是以在程式執行期間局部變量表的大小是不會改變的。

操作數棧,想必學過資料結構中的棧的朋友想必對表達式求值問題不會陌生,棧最典型的一個應用就是用來對表達式求值。想想一個線程執行方法的過程中,實際上就是不斷執行語句的過程,而歸根到底就是進行計算的過程。是以可以這麼說,程式中的所有計算過程都是在借助于操作數棧來完成的。

指向運作時常量池的引用,因為在方法執行的過程中有可能需要用到類中的常量,是以必須要有一個引用指向運作時常量。

方法傳回位址,當一個方法執行完畢之後,要傳回之前調用它的地方,是以在棧幀中必須儲存一個方法傳回位址。

由于每個線程正在執行的方法可能不同,是以每個線程都會有一個自己的Java棧,互不幹擾。

3. 本地方法棧

本地方法棧與Java棧的作用和原理非常相似。差別隻不過是Java棧是為執行Java方法服務的,而本地方法棧則是為執行本地方法(Native Method)服務的。在JVM規範中,并沒有對本地方發展的具體實作方法以及資料結構作強制規定,虛拟機可以自由實作它。在HotSopt虛拟機中直接就把本地方法棧和Java棧合二為一。

4. 堆

Java中的堆是用來存儲對象本身的以及數組,程式員基本不用去關心空間釋放的問題,Java的垃圾回收機制會自動進行處理。是以這部分空間也是Java垃圾收集器管理的主要區域。另外,堆是被所有線程共享的,在JVM中隻有一個堆。

5. 方法區

方法區在JVM中也是一個非常重要的區域,它與堆一樣,是被線程共享的區域。在方法區中,存儲了每個類的資訊(包括類的名稱、方法資訊、字段資訊)、靜态變量、常量以及編譯器編譯後的代碼等。

在Class檔案中除了類的字段、方法、接口等描述資訊外,還有一項資訊是常量池,用來存儲編譯期間生成的字面量和符号引用。

在方法區中有一個非常重要的部分就是運作時常量池,它是每一個類或接口的常量池的運作時表示形式,在類和接口被加載到JVM後,對應的運作時常量池就被建立出來。當然并非Class檔案常量池中的内容才能進入運作時常量池,在運作期間也可将新的常量放入運作時常量池中,比如String的intern方法。

二,JVM參數配置

用法:java-Xmx3550m -Xms3550m -Xmn2g -Xss128k

     Xss:每個線程的stack大小(棧)

     Xmx:JAVA HEAP的最大值、預設為實體記憶體的1/4

     Xms:JAVA HEAP的初始值,server端最好Xms與Xmx一樣

     Xmn:JAVA HEAP young區的大小

     XX:MetaspaceSize:class metadata的初始空間配額,以bytes為機關,達到該值就會觸發垃圾收集進行類型解除安裝,同時GC會對該值進行調整

     XX:MaxMetaspaceSize:可以為class metadata配置設定的最大空間。預設是沒有限制的。

注意:java8去掉了-XX:PermSize和-XX:MaxPermSize,新增了-XX:MetaspaceSize和-XX:MaxMetaspaceSize

通過設定JVM參數後,我們可以使用jmap -heap pid指令來檢視設定的性況,如下圖: 

玩轉JVM虛拟機:JVM記憶體結構

MaxHeapFreeRatio: GC後如果發現空閑堆記憶體占到整個預估堆記憶體的N%(百分比),則收縮堆記憶體的預估最大值, 預估堆記憶體是堆大小動态調控的重要選項之一. 堆記憶體預估最大值一定小于或等于固定最大值(-Xmx指定的數值). 前者會根據使用情況動态調大或縮小, 以提高GC回收的效率

MinHeapFreeRatio: GC後如果發現空閑堆記憶體占到整個預估堆記憶體的N%(百分比), 則放大堆記憶體的預估最大值

MaxHeapSize: 即-Xmx, 堆記憶體大小的上限

NewSize: 新生代預估堆記憶體占用的預設值

MaxNewSize: 新生代占整個堆記憶體的最大值

OldSize: 老年代的預設大小

NewRatio: 老年代對比新生代的空間大小, 比如2代表老年代空間是新生代的兩倍大小

SurvivorRatio: Eden/Survivor的值。8表示Survivor:Eden=1:8,因為survivor區有2個, 是以Eden的占比為8/10

MetaspaceSize: 配置設定給類中繼資料空間的初始大小

MaxMetaspaceSize: 配置設定給類中繼資料空間的最大值, 超過此值就會觸發FullGC. 此值僅受限于系統記憶體的大小, JVM會動态地改變此值

CompressedClassSpaceSize: 類指針壓縮空間大小, 預設為1G,在64位平台上指針壓縮預設是打開的

G1HeapRegionSize: G1區塊的大小, 取值為1M至32M. 其取值是要根據最小Heap大小劃分出2048個區塊

補充:

  1)使用-XX:+UseCompressedOops壓縮對象指針

     oops指的是普通對象指針(ordinary object pointers)。

     Java堆中對象指針會被壓縮成32位。

  2)使用-XX:+UseCompressedClassPointers選項來壓縮類指針

     對象中指向類中繼資料的指針會被壓縮成32位

     類指針壓縮空間會有一個基位址

  3)元空間和類指針壓縮空間的差別

      類指針壓縮空間隻包含類的中繼資料,比如InstanceKlass,ArrayKlass

      僅當打開了UseCompressedClassPointers選項才生效

      為了提高性能,Java中的虛方法表也存放到這裡

      而元空間包含類的其它比較大的中繼資料,比如方法,位元組碼,常量池等。

接下來我們再使用jstat -gcutil pid來看一下記憶體的使用情況,如下圖:

玩轉JVM虛拟機:JVM記憶體結構

    S0:Survivor 0區的空間使用率.

    S1: Survivor 1區的空間使用率.

    E: Eden區的空間使用率.

    O: 老年代的空間使用率.

    M: 中繼資料的空間使用率.

    CCS: 類指針壓縮空間使用率.

    YGC: 新生代GC次數.

    YGCT: 新生代GC總時長.

    FGC: Full GC次數.

    FGCT: Full GC總時長.

    GCT: 總共的GC時長.

三,常用檢視記憶體的指令

jinfo:可以輸出并修改運作時的java 程序的資訊。

jps:與unix上的ps類似,用來顯示本地的java程序,可以檢視本地運作着幾個java程式,并顯示他們的程序号。

jstat:一個極強的監視VM記憶體工具。可以用來監視VM記憶體内的各種堆和非堆的大小及其記憶體使用量。

jstat -class pid:顯示加載class的數量,及所占空間等資訊。

jstat -compiler pid:顯示VM實時編譯的數量等資訊。

jstat -gc pid:可以顯示gc的資訊,檢視gc的次數,及時間。

jstat -gcnew pid: new對象的資訊。

jstat -gcnewcapacity pid: new對象的資訊及其占用量。

jstat -gcold pid: old對象的資訊。

jstat -gcoldcapacity pid: old對象的資訊及其占用量。

jstat -gcpermcapacity pid: perm對象的資訊及其占用量。

jstat -util pid:統計gc資訊統計。

jstat -printcompilation pid:目前VM執行的資訊。

jstat -gcutil pid 1000 10 : 1000ms統計一次gc情況統計10次; 

Jstack:檢視jvm線程運作狀态,是否有死鎖現象等等資訊。

jmap:列印出某個java程序記憶體内的所有對象的情況,如:産生那些對象,及其數量。

指令:jmap -dump:format=b,file=heap.bin<pid>

file:儲存路徑及檔案名

pid:程序編号

jmap -histo:live  pid| less :堆中活動的對象以及大小

jmap -heap pid : 檢視堆的使用狀況資訊

jconsole:一個java GUI監視工具,可以以圖表化的形式顯示各種資料。并可通過遠端連接配接監視遠端的伺服器VM。

原文釋出時間為:2017-12-8

本文作者:楊彪@雲時代架構