【JVM】
- 記憶體結構
-
- 1.程式計數器(PC Registers)
- 2.本地方法棧(Native Method Stack)
- 3.方法區(Method Area)
-
- 3-1.運作時的常量池(Run-Time Constant Pool)
- 4.堆(Heap)
-
- 4-1.堆記憶體結構(新生代和老年代)
- 5.JVM棧(JVM Stack)
- JVM調優
- 記憶體模型
- 指針壓縮
記憶體結構
Name | 線程私有/共享 | 設定參數 | Function |
---|---|---|---|
程式計數器(PC Registers) | 私有 | 固定大小 | 儲存目前線程執行的方法 |
本地方法棧(Native Method Stack) | 私有 | 固定大小 | JNI調取本地方法 |
方法區(Method Area) | 共享 | -XX | static,class類結構,常量(池) |
堆(Heap) | 共享 | -Xms:初始堆值;-Xmx:最大堆值 | new出來的執行個體對象和數組 |
JVM棧(JVM Stack) | 私有 | -Xss | (每個線程)方法的棧幀、局部變量表、操作數棧 |
1.程式計數器(PC Registers)
- 一塊小記憶體,每個線程都有
- PC存儲目前方法:
- 線程正在執行的方法稱為該線程的目前方法
- 目前方法為本地(native)方法時,pc值未定義(undefined)
- 目前方法為非本地方法時,pc包含了目前正在執行指令的位址
- 目前唯一一塊不會引發OutOfMemoryError異常
2.本地方法棧(Native Method Stack)
-
存儲native方法的執行資訊(Java調用C程式執行代碼時的資訊),線程私有
(C函數就叫Native方法)
- 引發的異常:
- 棧的深度超過虛拟機規定深度,就會引發StackOverflowError異常
- 無法擴充記憶體,OutOfMemoryError異常
- 比如:安卓開發用到的C語言,都會使用Java語言JNI,就是Java語言去調用C語言
3.方法區(Method Area)
-
存儲JVM已經加載過類的結構
(類加載器:在我們程式運作的時候,需要加載很多類進來,比如rt.jar裡面那些基礎的類都要加載進來,對于一個JVM來說,它加載的類是有限制的,一個JVM它可能不能負擔很多很多類,這個就被方法區給受限,那麼方法區時存儲JVM已經加載過類的結構)
- 所有線程共享,所有線程加載過的類都會放在方法區裡面
- 那麼,運作時的常量池、類資訊、常量、靜态變量等
- 在Java(JVM)啟動的時候呢,會建立方法區,邏輯上屬于堆記憶體的一部分
- (當類的位元組碼檔案被加載到記憶體(?這裡指方法區)時,類的執行個體方法不會被配置設定入口位址,在該類建立對象後,類中的執行個體方法才配置設定入口位址(堆)。)
- 很少做垃圾回收
- 無法滿足記憶體配置設定要求,OutOfMemoryError異常
- “-XX”參數設定大小
- static靜态變量或方法不要定義太多,因為GC垃圾回收機制是不會回收的
(什麼時候會加載幾十萬個類呢?比如說我們通常會将JSP編譯成Servlet,那麼,所有的網頁頁面都會被編譯成一個Java類,如果一個系統裡面,它的網頁數量過多,它用到的Java類就會非常多,那麼這個方法區的大小就會對程式的運作就有影響)
3-1.運作時的常量池(Run-Time Constant Pool)
- Class檔案中常量池的運作時表示
-
(每個Class裡面都有一個Constant Pool,那麼在運作的時候,這個類被加載進來,Class裡的Constant Pool就會被映射到這塊記憶體區域上)編譯期确定的常量
屬于方法區的一部分
動态性
- Java語言并不要求常量一定隻有在編譯期産生(不一定都是編譯期就能确定的常量)
-
比如String.intern方法也可以将字元串放入運作時的常量池
引發的異常:
無法滿足記憶體配置設定要求,OutOfMemoryError異常
4.堆(Heap)
- 虛拟機啟動時開始建立,
- 什麼時候會把資料放在堆記憶體上呢?
- 當我們在程式裡面采用new操作,或者定義一個數組的時候,那我們都是在堆上配置設定記憶體的
- 對象執行個體和數組都是堆上配置設定記憶體的
- 占了太大比例,是以,這一塊也是垃圾回收的主要區域:沒有用的垃圾對象進行回收
-
設定大小
“-Xms”初始堆值,“-Xmx”最大堆值
-
引發的異常
無法滿足記憶體配置設定要求,OutOfMemoryError異常:
1:程式消耗的性能太多,記憶體不夠用
2:記憶體洩漏,有些對象沒有及時地被垃圾回收
那麼需要對程式進行細微的檢查
- 因為線程共享,這裡定義的成員變量就是共享資料,存線上程安全的問題
4-1.堆記憶體結構(新生代和老年代)
5.JVM棧(JVM Stack)
- (這個變量是放在棧裡面的,這個變量是放在堆裡面的,實際上就是指JVM棧包含的)
- 每個線程有自己獨立的一個Java虛拟機棧:有10個線程就有10個JVM棧
- “-Xss”設定每個線程堆棧大小
-
Java方法的執行基于棧
每個方法從調用到完成對應一個棧幀在棧中入棧、出棧的過程(壓棧)
-
棧幀存儲局部變量表、操作數棧等
局部變量表存放方法中存放在“棧”裡面的東西,也就是局部變量之類的東西
- (定義一個小int,一個byte這一類的,我們都是在JVM棧上面來定義)
-
局部變量
(在Java位元組碼那一章裡面可以知道棧幀,我們在每一個棧幀裡面,都存儲着局部變量表、操作數棧等等,你把一個class檔案進行反編譯以後,你就會看到裡面的Code區域都有很多指令,那麼每個指令,實際上,都是從局部變量表裡面,把資料搬到操作數棧上面處理,處理完都又把它放回局部變量表裡面去)
- 在多線程情況下,共享同一個局部變量,不會發生線程安全。因為棧就是每個線程都有自己的局部變量表
-
引發的異常:
棧的深度超過虛拟機規定深度,就會引發StackOverflowError異常
(使用過多的變量)無法擴充記憶體,OutOfMemoryError異常
JVM調優
- 一般:初始堆記憶體(-Xms)和最大堆記憶體(-Xmx)都是一緻
- 初始堆記憶體設定比較小的話,垃圾回收機制會比較頻繁去回收,這樣會耗程式。
- -XMS和-XMX設定成一緻,這樣做的好處是可以減少程式運作時垃圾回收次數,進而提高效率
- 最大堆記憶體跟電腦硬體配置:一般接近電腦組態
-
作業系統配置設定給每個程序的記憶體是有限制的,譬如32位的Windows限制為2GB。
虛拟機提供了參數來控制Java堆和方法區的這兩部分記憶體的最大值。剩餘的記憶體為2GB(作業系統限制)減 Xmx(最大堆容量),再減MaxPermSize(最大方法區容量),程式計數器消耗記憶體很小,可以忽略掉
-
- 設定新生代大小與老年代優化參數:
- "-Xmn"新生代大小,一般設為整個堆的1/3到1/4左右
- “-XX:SurvivorRatio”:設定新生代中edenhe from/to空間的比例關系n/1
- “-XX:NewRatio”:老/新
- 需求:可能會有很多經常使用的對象:這時,老年代越大越好,新生代就越小,盡量減少老年代的GC
- 需求:有些對象不怎麼經常使用,但是使用新對象特别多。這時新生代要大點好
記憶體模型
指針壓縮
線性位址不開是8位元組,開是4位元組
線性位址8位元組=64bit=48在用+16保留
2^48=256T
對象記憶體結構
空對象:
未開啟指針壓縮 16B
-XX:+/-UseCompressedOops
開啟指針壓縮:
16B=8B(Mark Word)+4b(KClass pointer)+0(執行個體資料)+4B(對齊填充)
普通對象:
對象頭裡面才有填充
記憶體位址
16=10
24從48算起=30
oop多少位 32+3=35
2^35
擴容補0
jol
虛拟機棧溢出
- 調用鍊過長
- 死循環
- 無限遞歸
-
java -XX:+PrintFlagsFinal -version
grep ThreadStack
-Xss160k