JVM 堆記憶體和非堆記憶體
轉載自:http://www.importnew.com/27645.html
堆和非堆記憶體
按照官方的說法:“Java 虛擬機器具有一個堆(Heap),堆是執行時資料區域,所有類例項和陣列的記憶體均從此處分配。堆是在 Java 虛擬機器啟動時建立的。”“在JVM中堆之外的記憶體稱為非堆記憶體(Non-heap memory)”。
JVM主要管理兩種型別的記憶體:堆和非堆。
Heap memory Code Cache Eden Space Survivor Space Tenured Gen non-heap memory Perm Gen native heap?(I guess)
堆記憶體
Java 虛擬機器具有一個堆,堆是執行時資料區域,所有類例項和陣列的記憶體均從此處分配。堆是在 Java 虛擬機器啟動時建立的。物件的堆記憶體由稱為垃圾回收器的自動記憶體管理系統回收。
堆的大小可以固定,也可以擴大和縮小。堆的記憶體不需要是連續空間。
非堆記憶體
Java 虛擬機器管理堆之外的記憶體(稱為非堆記憶體)。
Java 虛擬機器具有一個由所有執行緒共享的方法區。方法區屬於非堆記憶體。它儲存每個類結構,如執行時常數池、欄位和方法資料,以及方法和構造方法的程式碼。它是在 Java 虛擬機器啟動時建立的。
方法區在邏輯上屬於堆,但 Java 虛擬機器實現可以選擇不對其進行回收或壓縮。與堆類似,方法區的大小可以固定,也可以擴大和縮小。方法區的記憶體不需要是連續空間。
除了方法區外,Java 虛擬機器實現可能需要用於內部處理或優化的記憶體,這種記憶體也是非堆記憶體。例如,JIT 編譯器需要記憶體來儲存從 Java 虛擬機器程式碼轉換而來的本機程式碼,從而獲得高效能。
幾個基本概念
PermGen space:全稱是Permanent Generation space,即永久代。就是說是永久儲存的區域,用於存放Class和Meta資訊,Class在被Load的時候被放入該區域,GC(Garbage Collection)應該不會對PermGen space進行清理,所以如果你的APP會LOAD很多CLASS的話,就很可能出現PermGen space錯誤。
Heap space:存放Instance。
Java Heap分為3個區,Young即新生代,Old即老生代和Permanent。
Young儲存剛例項化的物件。當該區被填滿時,GC會將物件移到Old區。Permanent區則負責儲存反射物件。
堆記憶體分配
- JVM初始分配的堆記憶體由-Xms指定,預設是實體記憶體的1/64;
- JVM最大分配的堆記憶體由-Xmx指定,預設是實體記憶體的1/4。
- 預設空餘堆記憶體小於40%時,JVM就會增大堆直到-Xmx的最大限制;
- 空餘堆記憶體大於70%時,JVM會減少堆直到-Xms的最小限制。
- 因此伺服器一般設定-Xms、-Xmx 相等以避免在每次GC 後調整堆的大小。
- 說明:如果-Xmx 不指定或者指定偏小,應用可能會導致java.lang.OutOfMemory錯誤,此錯誤來自JVM,不是Throwable的,無法用try…catch捕捉。
非堆記憶體分配
- JVM使用-XX:PermSize設定非堆記憶體初始值,預設是實體記憶體的1/64;
- 由XX:MaxPermSize設定最大非堆記憶體的大小,預設是實體記憶體的1/4。
- 還有一說:MaxPermSize預設值和-server -client選項相關,-server選項下預設MaxPermSize為64m,-client選項下預設MaxPermSize為32m。這個我沒有實驗。
- XX:MaxPermSize設定過小會導致java.lang.OutOfMemoryError: PermGen space 就是記憶體益出。
- 為什麼會記憶體益出:
- 這一部分記憶體用於存放Class和Meta的資訊,Class在被 Load的時候被放入PermGen space區域,它和存放Instance的Heap區域不同。
- GC(Garbage Collection)不會在主程式執行期對PermGen space進行清理,所以如果你的APP會LOAD很多CLASS 的話,就很可能出現PermGen space錯誤。
- 這種錯誤常見在web伺服器對JSP進行pre compile的時候。
JVM記憶體限制(最大值)
- 首先JVM記憶體限制於實際的最大實體記憶體,假設實體記憶體無限大的話,JVM記憶體的最大值跟作業系統有很大的關係。簡單的說就32位處理器雖然可控記憶體空間有4GB,但是具體的作業系統會給一個限制,這個限制一般是2GB-3GB(一般來說Windows系統下為1.5G-2G,Linux系統下為2G-3G),而64bit以上的處理器就不會有限制了。
- 為什麼有的機器我將-Xmx和-XX:MaxPermSize都設定為512M之後Eclipse可以啟動,而有些機器無法啟動?
通過上面對JVM記憶體管理的介紹我們已經瞭解到JVM記憶體包含兩種:堆記憶體和非堆記憶體,另外JVM最大記憶體首先取決於實際的實體記憶體和作業系統。所以說設定VM引數導致程式無法啟動主要有以下幾種原因:
- 引數中-Xms的值大於-Xmx,或者-XX:PermSize的值大於-XX:MaxPermSize;
- -Xmx的值和-XX:MaxPermSize的總和超過了JVM記憶體的最大限制,比如當前作業系統最大記憶體限制,或者實際的實體記憶體等等。說到實際實體記憶體這裡需要說明一點的是,如果你的記憶體是1024MB,但實際系統中用到的並不可能是1024MB,因為有一部分被硬體佔用了。
- 如果你有一個雙核的CPU,也許可以嘗試這個引數: -XX:+UseParallelGC 讓GC可以更快的執行。(只是JDK 5裡對GC新增加的引數)
- 如果你的WEB APP下都用了大量的第三方jar,其大小超過了伺服器jvm預設的大小,那麼就會產生記憶體益出問題了。解決方法: 設定MaxPermSize大小。
- 增加伺服器啟動的JVM引數設定: -Xms128m -Xmx256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m
- 如tomcat,修改TOMCAT_HOME/bin/catalina.sh,在echo “Using CATALINA_BASE: $CATALINA_BASE”上面加入以下行:JAVA_OPTS=”-server -XX:PermSize=64M -XX:MaxPermSize=128m
- 建議:將相同的第三方jar檔案移置到tomcat/shared/lib目錄下,這樣可以減少jar 文件重複佔用記憶體
JVM記憶體設定引數
- 記憶體設定引數
設定項 | 說明 |
---|---|
-Xms512m | 表示JVM初始分配的堆記憶體大小為512m(JVM Heap(堆記憶體)最小尺寸,初始分配) |
-Xmx1024m | JVM最大允許分配的堆記憶體大小為1024m,按需分配(JVM Heap(堆記憶體)最大允許的尺寸,按需分配) |
-XX:PermSize=512M | JVM初始分配的非堆記憶體 |
-XX:MaxPermSize=1024M | JVM最大允許分配的非堆記憶體,按需分配 |
-XX:NewSize/-XX:MaxNewSize | 定義YOUNG段的尺寸,NewSize為JVM啟動時YOUNG的記憶體大小; |
MaxNewSize為最大可佔用的YOUNG記憶體大小。 | |
-XX:SurvivorRatio | 設定YOUNG代中Survivor空間和Eden空間的比例 |
- 說明:
- 如果-Xmx不指定或者指定偏小,應用可能會導致java.lang.OutOfMemory錯誤,此錯誤來自JVM不是Throwable的,無法用try…catch捕捉。
- PermSize和MaxPermSize指明虛擬機器為java永久生成物件(Permanate generation)如,class物件、方法物件這些可反射(reflective)物件分配記憶體限制,這些記憶體不包括在Heap(堆記憶體)區之中。
- -XX:MaxPermSize分配過小會導致:java.lang.OutOfMemoryError: PermGen space。
- MaxPermSize預設值和-server -client選項相關:-server選項下預設MaxPermSize為64m、-client選項下預設MaxPermSize為32m。
- 申請一塊記憶體的過程
- JVM會試圖為相關Java物件在Eden中初始化一塊記憶體區域
- 當Eden空間足夠時,記憶體申請結束。否則到下一步
- JVM試圖釋放在Eden中所有不活躍的物件(這屬於1或更高階的垃圾回收);釋放後若Eden空間仍然不足以放入新物件,則試圖將部分Eden中活躍物件放入Survivor區/OLD區
- Survivor區被用來作為Eden及OLD的中間交換區域,當OLD區空間足夠時,Survivor區的物件會被移到Old區,否則會被保留在Survivor區
- 當OLD區空間不夠時,JVM會在OLD區進行完全的垃圾收集(0級)
- 完全垃圾收集後,若Survivor及OLD區仍然無法存放從Eden複製過來的部分物件,導致JVM無法在Eden區為新物件建立記憶體區域,則出現”out of memory錯誤”
resin伺服器典型的響應時間優先型的jvm配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
-Xmx2000M -Xms2000M -Xmn500M
-XX:PermSize=250M -XX:MaxPermSize=250M
-Xss256K
-XX:+DisableExplicitGC
-XX:SurvivorRatio=
1
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=
0
-XX:+CMSClassUnloadingEnabled
-XX:LargePageSizeInBytes=128M
-XX:+UseFastAccessorMethods
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=
60
-XX:SoftRefLRUPolicyMSPerMB=
0
-XX:+PrintClassHistogram
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintHeapAtGC
-Xloggc:log/gc.log
|
記憶體回收演算法
Java中有四種不同的回收演算法,對應的啟動引數為:
1 2 3 4 |
–XX:+UseSerialGC
–XX:+UseParallelGC
–XX:+UseParallelOldGC
–XX:+UseConcMarkSweepGC
|
Serial Collector
大部分平臺或者強制 java -client 預設會使用這種。
young generation演算法 = serial
old generation演算法 = serial (mark-sweep-compact)
這種方法的缺點很明顯, stop-the-world, 速度慢。伺服器應用不推薦使用。
Parallel Collector
在linux x64上預設是這種,其他平臺要加 java -server 引數才會預設選用這種。
young = parallel,多個thread同時copy
old = mark-sweep-compact = 1
優點:新生代回收更快。因為系統大部分時間做的gc都是新生代的,這樣提高了throughput(cpu用於非gc時間)
缺點:當執行在8G/16G server上old generation live object太多時候pause time過長
Parallel Compact Collector (ParallelOld)
young = parallel = 2
old = parallel,分成多個獨立的單元,如果單元中live object少則回收,多則跳過
優點:old old generation上效能較 parallel 方式有提高
缺點:大部分server系統old generation記憶體佔用會達到60%-80%, 沒有那麼多理想的單元live object很少方便迅速回收,同時compact方面開銷比起parallel並沒明顯減少。
Concurrent Mark-Sweep(CMS) Collector
young generation = parallel collector = 2
old = cms
同時不做 compact 操作。
優點:pause time會降低, pause敏感但CPU有空閒的場景需要建議使用策略4.
缺點:cpu佔用過多,cpu密集型伺服器不適合。另外碎片太多,每個object的儲存都要通過連結串列連續跳n個地方,空間浪費問題也會增大。
記憶體監控方法
- jmap -heap 檢視java 堆(heap)使用情況
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
jmap -heap pid
using thread-local object allocation.
Parallel GC with
4
thread(s) #GC 方式
Heap Configuration: #堆記憶體初始化配置
MinHeapFreeRatio=
40
#對應jvm啟動引數-XX:MinHeapFreeRatio設定JVM堆最小空閒比率(
default
40
)
MaxHeapFreeRatio=
70
#對應jvm啟動引數 -XX:MaxHeapFreeRatio設定JVM堆最大空閒比率(
default
70
)
MaxHeapSize=
512
.0MB #對應jvm啟動引數-XX:MaxHeapSize=設定JVM堆的最大大小
NewSize =
1
.0MB #對應jvm啟動引數-XX:NewSize=設定JVM堆的‘新生代’的預設大小
MaxNewSize =4095MB #對應jvm啟動引數-XX:MaxNewSize=設定JVM堆的‘新生代’的最大大小
OldSize =
4
.0MB #對應jvm啟動引數-XX:OldSize=<value>:設定JVM堆的‘老生代’的大小
NewRatio =
8
#對應jvm啟動引數-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
SurvivorRatio =
8
#對應jvm啟動引數-XX:SurvivorRatio=設定年輕代中Eden區與Survivor區的大小比值
PermSize=
16
.0MB #對應jvm啟動引數-XX:PermSize=<value>:設定JVM堆的‘永生代’的初始大小
MaxPermSize=
64
.0MB #對應jvm啟動引數-XX:MaxPermSize=<value>:設定JVM堆的‘永生代’的最大大小
Heap Usage: #堆記憶體分步
PS Young Generation
Eden Space: #Eden區記憶體分佈
capacity =
20381696
(
19
.4375MB) #Eden區總容量
used =
20370032
(
19
.426376342773438MB) #Eden區已使用
free =
11664
(
0
.0111236572265625MB) #Eden區剩餘容量
99.94277218147106
% used #Eden區使用比率
From Space: #其中一個Survivor區的記憶體分佈
capacity =
8519680
(
8
.125MB)
used =
32768
(
0
.03125MB)
free =
8486912
(
8
.09375MB)
0.38461538461538464
% used
To Space: #另一個Survivor區的記憶體分佈
capacity =
9306112
(
8
.875MB)
used =
0
(
0
.0MB)
free =
9306112
(
8
.875MB)
0.0
% used
PS Old Generation #當前的Old區記憶體分佈
capacity =
366280704
(
349
.3125MB)
used =
322179848
(
307
.25464630126953MB)
free =
44100856
(
42
.05785369873047MB)
87.95982001825573
% used
PS Perm Generation #當前的 “永生代” 記憶體分佈
capacity =
32243712
(
30
.75MB)
used =
28918584
(
27
.57891082763672MB)
free =
3325128
(
3
.1710891723632812MB)
89.68751488662348
% used
|
- JVM記憶體監控工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<%@ page
import
=
"java.lang.management.*"
%>
<%@ page
import
=
"java.util.*"
%>
<html>
<head>
<title>JVM Memory Monitor</title>
</head>
<body>
<table border=
"0"
width=
"100%"
>
<tr><td colspan=
"2"
align=
"center"
><h3>Memory MXBean</h3></td></tr>
<tr><td width=
"200"
>Heap Memory Usage</td><td><%=ManagementFactory.getMemoryMXBean().getHeapMemoryUsage()%></td></tr>
<tr><td>Non-Heap Memory Usage</td><td><%=ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage()%></td></tr>
<tr><td colspan=
"2"
> </td></tr>
<tr><td colspan=
"2"
align=
"center"
><h3>Memory Pool MXBeans</h3></td></tr>
<%
Iterator iter = ManagementFactory.getMemoryPoolMXBeans().iterator();
while
(iter.hasNext()) {
MemoryPoolMXBean item = (MemoryPoolMXBean) iter.next();
%>
<tr><td colspan=
"2"
>
<table border=
"0"
width=
"100%"
style=
"border: 1px #98AAB1 solid;"
>
<tr><td colspan=
"2"
align=
"center"
><b><%= item.getName() %></b></td></tr>
<tr><td width=
"200"
>Type</td><td><%= item.getType() %></td></tr>
<tr><td>Usage</td><td><%= item.getUsage() %></td></tr>
<tr><td>Peak Usage</td><td><%= item.getPeakUsage() %></td></tr>
<tr><td>Collection Usage</td><td><%= item.getCollectionUsage() %></td></tr>
</table>
</td></tr>
<tr><td colspan=
"2"
> </td></tr>
<%} %>
</table>
</body>
|