java虛擬機內存參數設置及常見錯誤總結

JVM--內存參數設置及常見錯誤總結

一、  JVM規範

JVM規範對Java運行時的內存劃定了幾塊區域(詳見這裏),有:JVM棧(Java Virtual Machine Stacks)、堆(Heap)、方法區(Method Area)、常量池(Runtime Constant Pool)、本地方法棧(Native Method Stacks),但對各塊區域的內存佈局和地址空間卻沒有明確規定,而留給各JVM廠商發揮的空間。

二、  HotSpot JVM

Sun自家的HotSpot JVM實現對堆內存結構有相對明確的說明。按照HotSpot JVM的實現,堆內存分爲3個代:Young Generation、Old(Tenured) Generation、Permanent Generation。衆所周知,GC(垃圾收集)就是發生在堆內存這三個代上面的。Young用於分配新的Java對象,其又被分爲三個部分:Eden Space和兩塊Survivor Space(稱爲From和To),Old用於存放在GC過程中從Young Gen中存活下來的對象,Permanent用於存放JVM加載的class等元數據。詳情參見HotSpot內存管理白皮書

     1.Java Heap分爲3個區

         1).Young(分爲兩個同等大小的survior區和一個eden區,JVM默認分配內存大小爲survior:eden = 1 : 8, 可配置)

         2).Old(一個Old區,JVM默認分配內存大小爲 Old:Young = 2:1, 可配置)

         3).Permanent

         Young保存剛實例化的對象。當該區被填滿時,GC會將對象移到Old區(按照一定的算法,如GC超過15次後某一對象還存活,則會移動到Old區,可配置)

     2.JVM有2個GC線程 (未查找到詳細資料)

         第一個線程負責回收Heap的Young區
         第二個線程在Heap不足時,遍歷Heap,將Young 區升級爲Older區

         以下是我的機器的執行該命令查看到的JVM相關線程:jstack -l pid

 

"VMThread" prio=10 tid=0x00007fa2c4061000 nid=0x18431 runnable 
"GCtask thread#0 (ParallelGC)" prio=10 tid=0x00007fa2c401a000 nid=0x1842frunnable
"GCtask thread#1 (ParallelGC)" prio=10 tid=0x00007fa2c401b800 nid=0x18430runnable
"VMPeriodic Task Thread" prio=10 tid=0x00007fa2c4097800 nid=0x18438 waitingon condition

 

 

 

三、  JVM參數設置

非穩態選項使用說明:
-XX:+<option> 啓用option
-XX:-<option> 不啓用option
-XX:<option>=<number> 設定option的值爲數字類型,可跟單位,例如 32k, 1024m, 2g
-XX:<option>=<string> 設定option的值爲字符串,例如-XX:HeapDumpPath=./dump.core

性能選項

選項與默認值 默認值與限制 描述
-Xms 初始堆大小 默認(MinHeapFreeRatio參數可以調整)空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制.
-Xmx 最大堆大小 默認(MaxHeapFreeRatio參數可以調整)空餘堆內存大於70%時,JVM會減少堆直到 -Xms的最小限制.
注意:此處的大小是(eden+ 2 survivor space).與jmap -heap中顯示的New gen是不同的。
整個堆大小=年輕代大小 + 年老代大小 + 持久代大小.
-Xmn 年輕代大小(1.4or lator) 增大年輕代後,將會減小年老代大小.此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8
-Xss 每個線程的堆棧大小 JDK5.0以後每個線程堆棧大小爲1M,以前每個線程堆棧大小爲256K.更具應用的線程所需內存大小進行 調整.在相同物理內存下,減小這個值能生成更多的線程.但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右
一般小的應用, 如果棧不是很深, 應該是128k夠用的 大的應用建議使用256k。這個選項對性能影響比較大,需要嚴格的測試。(校長)
和threadstacksize選項解釋很類似,官方文檔似乎沒有解釋,在論壇中有這樣一句話:"”
-Xss is translated in a VM flag named ThreadStackSize”
一般設置這個值就可以了。
-XX:MaxTenuringThreshold 垃圾最大年齡 如果設置爲0的話,則年輕代對象不經過Survivor區,直接進入年老代. 對於年老代比較多的應用,可以提高效率.如果將此值設置爲一個較大值,則年輕代對象會在Survivor區進行多次複製,這樣可以增加對象再年輕代的存活 時間,增加在年輕代即被回收的概率
該參數只有在串行GC時纔有效.
-XX:ParallelGCThreads 並行收集器的線程數 此值最好配置與處理器數目相等 同樣適用於CMS
-XX:+AggressiveOpts JDK 5 update 6後引入,但需要手動啓用。 啓用JVM開發團隊最新的調優成果。例如編譯優化,偏向鎖,並行年老代收集等。
JDK6默認啓用。
-XX:LargePageSizeInBytes=4m 默認4m,amd64位:2m 設置堆的內存頁大小。
-XX:MaxHeapFreeRatio=70 70 GC後,如果發現空閒堆內存佔到整個預估堆內存的70%,則收縮堆內存預估最大值。
什麼是預估堆內存?
預估堆內存是堆大小動態調控的重要選項之一。堆內存預估最大值一定小於或等於固定最大值(-Xmx指定的數值)。前者會根據使用情況動態調大或縮小,以提高GC回收的效率。
-XX:NewSize 設置年輕代大小(for 1.3/1.4)
-XX:MaxNewSize=size 1.3.1 Sparc: 32m 新生代佔整個堆內存的最大值。
1.3.1 x86: 2.5m
-XX:MaxPermSize=64m 5.0以後: 64 bit VMs會增大預設值的30% Perm佔整個堆內存的最大值。
1.4 amd64: 96m
1.3.1 -client: 32m
其他默認 64m
-XX:MinHeapFreeRatio=40 40 GC後,如果發現空閒堆內存佔到整個預估堆內存的40%,則放大堆內存的預估最大值,但不超過固定最大值。
關聯選項:
-XX:MaxHeapFreeRatio=70
-XX:NewRatio=2 Sparc -client: 8

新生代和年老代的堆內存佔用比例。
這裏的2表示,則Old Generation是 Yong Generation的2倍,即Yong Generation佔據內存的1/3

x86 -server: 8
x86 -client: 12
-client: 4 (1.3)
8 (1.3.1+)
x86: 12
其他默認 2
-XX:NewSize=2.125m 5.0以後: 64 bit Vms會增大預設值的30% 新生代預估堆內存佔用的默認值。(什麼是預估堆內存?見 -XX:MaxHeapFreeRatio 處的描述)
x86: 1m
x86, 5.0以後: 640k
其他默認 2.125m
-XX:ReservedCodeCacheSize=32m    Solaris 64-bit, amd64, -server x86: 48m 設置代碼緩存的最大值,編譯用。
1.5.0_06之前, Solaris 64-bit amd64: 1024m
其他默認 32m
-XX:SurvivorRatio=8 Solaris amd64: 6 Eden與Survivor的佔用比例。這裏的8表示,一個survivor區佔用 1/8 的新生代內存,因爲survivor有2個,所以是 2/8,那麼Eden的佔比爲 6/8。
Sparc in 1.3.1: 25
Solaris platforms5.0以前: 32
其他默認 8
-XX:TargetSurvivorRatio=50 50 實際使用的survivor空間大小佔比。默認是50%,最高90%。
-XX:ThreadStackSize=512 Sparc: 512 線程堆棧大小
Solaris x86: 320(5.0以前 256)
Sparc 64 bit: 1024
Linux amd64: 1024 (5.0 以前 0)
其他默認 512.
-XX:+UseBiasedLocking JDK 5 update 6後引入,但需要手動啓用。 啓用偏向鎖。
JDK6默認啓用。
-XX:+UseFastAccessorMethods 默認啓用 啓用原始類型的getter方法優化。
-XX:-UseISM 默認啓用 啓用solaris的ISM。
詳見Intimate Shared Memory.
-XX:+UseLargePages JDK 5 update 5後引入,但需要手動啓用。 啓用大內存分頁。
JDK6默認啓用。
-XX:+UseMPSS 1.4.1 之前: 不啓用 啓用solaris的MPSS,不能與ISM同時使用。
其餘版本默認啓用
-XX:+StringCache 默認啓用 啓用字符串緩存。
-XX:AllocatePrefetchLines=1 1 與機器碼指令預讀相關的一個選項,資料比較少,本文檔不做解釋。有興趣的朋友請自行閱讀官方doc。
-XX:AllocatePrefetchStyle=1 1 與機器碼指令預讀相關的一個選項,資料比較少,本文檔不做解釋。有興趣的朋友請自行閱讀官方doc。
-XX:-AllowUserSignalHandlers 限於Linux和Solaris,默認不啓用 允許爲java進程安裝信號處理器。
-XX:-DisableExplicitGC 默認不啓用 禁止在運行期顯式地調用 System.gc()。
開啓該選項後,GC的觸發時機將由Garbage Collector全權掌控。
需要注意的是,你程序裏沒調用System.gc(),你依賴的框架,工具可能在使用。例如RMI。請仔細權衡禁用帶來的影響。
-XX:-RelaxAccessControlCheck 默認不啓用 在Class校驗器裏,放鬆對訪問控制的檢查。作用與reflection裏的setAccessible類似。
-XX:-UseConcMarkSweepGC 默認不啓用 啓用CMS低停頓垃圾收集器。
-XX:-UseParallelGC 默認不啓用,-server時啓用 策略爲新生代使用並行清除,年老代使用單線程Mark-Sweep-Compact清除的垃圾收集器。
-XX:-UseParallelOldGC 默認不啓用 策略爲老年代和新生代都使用並行清除的垃圾收集器。
-XX:-UseSerialGC 默認不啓用,-client時啓用 使用串行垃圾收集器。
-XX:+UseSplitVerifier java5默認不啓用,java6默認啓用 使用新的Class類型校驗器 。
什麼是新Class類型校驗器?
新Class類型校驗器,將老的校驗步驟拆分成兩步:
1,類型推斷。2,類型校驗。
新類型校驗器通過在javac編譯時嵌入類型信息到bytecode中,省略了類型推斷這一步,從而提升了classloader的性能。
Classload順序:load -> verify -> prepare -> resove -> init
關聯選項:
-XX:+FailOverToOldVerifier
-XX:+FailOverToOldVerifier Java6新引入選項,默認啓用 如果新的Class校驗器檢查失敗,則使用老的校驗器。
關聯選項:
-XX:+UseSplitVerifier
-XX:+HandlePromotionFailure     java5以前是默認不啓用,java6默認啓用 關閉新生代收集擔保。
什麼是新生代收集擔保?
在一次理想化的minor gc中,活躍對象會從Eden和First Survivor中被複制到Second Survivor。然而,Second Survivor不一定能容納所有的活躍對象。爲了確保minor gc能夠順利完成,需要在年老代中保留一塊足以容納所有活躍對象的內存空間。這個預留的操作,被稱之爲新生代收集擔保(New Generation Guarantee)。當預留操作無法完成時,就會觸發major gc(full gc)。
爲什麼要關閉新生代收集擔保?
因爲在年老代中預留的空間大小,是無法精確計算的。爲了確保極端情況的發生,GC參考了最壞情況下的新生代內存佔用,即Eden+First Survivor。這種策略無疑是在浪費年老代內存,並從時序角度看,可能提前觸發Full GC。爲了避免如上情況的發生,JVM允許開發者關閉新生代收集擔保。
在開啓本選項後,minotr gc將不再提供新生代收集擔保,而是在出現survior或年老代不夠用時,拋出promotion failed異常。
-XX:+UseSpinning java1.4.2和1.5需要手動啓用, java6默認已啓用 啓用多線程自旋鎖優化。
自旋鎖優化原理
大家知道,Java的多線程安全是基於Lock機制實現的,而Lock的性能往往不如人意。原因是,monitorenter與monitorexit這兩個控制多線程同步的bytecode原語,是JVM依賴操作系統互斥(mutex)來實現的。互斥是一種會導致線程掛起,並在較短的時間內又需要重新調度回原線程的,較爲消耗資源的操作。爲了避免進入OS互斥,Java6的開發者們提出了自旋鎖優化方法。自旋鎖優化的原理是在線程進入OS互斥前,通過CAS自旋一定的次數來檢測鎖的釋放。如果在自旋次數未達到預訂值前,發現鎖已被釋放,則會立即持有該鎖。
CAS檢測鎖的原理詳見: 關聯選項:
-XX:PreBlockSpin=10
-XX:PreBlockSpin=10 -XX:+UseSpinning必須先啓用,對於java6來說已經默認啓用了,這裏默認自旋10次 控制多線程自旋鎖優化的自旋次數。(什麼是自旋鎖優化?見 -XX:+UseSpinning 處的描述)
關聯選項:
-XX:+UseSpinning
-XX:+ScavengeBeforeFullGC     默認啓用 在Full GC前觸發一次Minor GC。
-XX:+UseGCOverheadLimit 默認啓用 限制GC的運行時間。如果GC耗時過長,就拋OOM。
-XX:+UseTLAB 1.4.2以前和使用-client選項時,默認不啓用,其餘版本默認啓用 啓用線程本地緩存區(Thread Local)。
-XX:+UseThreadPriorities 默認啓用 使用本地線程的優先級。
-XX:+UseAltSigs 限於Solaris,默認啓用 爲了防止與其他發送信號的應用程序衝突,允許使用候補信號替代 SIGUSR1和SIGUSR2。
-XX:+UseBoundThreads 限於Solaris, 默認啓用 綁定所有的用戶線程到內核線程。
減少線程進入飢餓狀態(得不到任何cpu time)的次數。
-XX:+UseLWPSynchronization 限於solaris,默認啓用 使用輕量級進程(內核線程)替換線程同步。
-XX:+MaxFDLimit 限於Solaris,默認啓用 設置java進程可用文件描述符爲操作系統允許的最大值。
-XX:+UseVMInterruptibleIO 限於solaris,默認啓用 在solaris中,允許運行時中斷線程 。
-XX:CMSInitiatingOccupancyFraction=70 92 使用cms作爲垃圾回收 使用70%後開始CMS收集 爲了保證不出現promotion failed(見下面介紹)錯誤,該值的設置需要滿足以下公式CMSInitiatingOccupancyFraction計算公式

 

調試選項

選項與默認值 默認值與限制 描述
-XX:-CITime 1.4引入。 打印JIT編譯器編譯耗時。
默認啓用
-XX:ErrorFile=./hs_err_pid<pid>.log Java 6引入。 如果JVM crash後,將錯誤日誌輸出到指定目錄。
-XX:-ExtendedDTraceProbes Java6引入,限於solaris 啓用dtrace診斷。
默認不啓用
-XX:HeapDumpPath=./java_pid<pid>.hprof    默認是java進程啓動位置,即user.dir 堆內存快照的存儲路徑。
什麼是堆內存快照?
當java進程因OOM或crash被強制退出後,生成hprof(Heap PROFling)格式的堆快照文件。用於出問題後調試,診斷。文件名一般爲java_<pid>_<date>_<time>_heapDump.hprof.
解析快照文件,可以使用 jhat, eclipse MAT,gdb等工具。
-XX:-HeapDumpOnOutOfMemoryError 1.4.2 update12 和 5.0 update 7 引入。 在OOM時,輸出一個dump.core文件,記錄當時的堆內存快照(什麼是堆內存快照? 見 -XX:HeapDumpPath 處的描述)。
默認不啓用
-XX:OnError="<cmd args>;<cmd args>" 1.4.2 update 9引入 當java每拋出一個ERROR時,運行指定命令行指令集。指令集是與OS環境相關的,在linux下多數是bash腳本,windows下是某個dos批處理。
-XX:OnOutOfMemoryError="<cmd args>;<cmd args>" 1.4.2 update 12和java6時引入 當第一次OOM時,運行指定命令行指令集。指令集是與OS環境相關的,在linux下多數是bash腳本,windows下是某個dos批處理。
-XX:-PrintClassHistogram 默認不啓用 打印class柱狀圖,圖中除了class,還有該class的instance統計信息。
Windows下, 按ctrl-break時。
Linux下是執行kill -3,或發送SIGQUIT信號。
Jmap –histo pid也實現了相同的功能。詳見 http://java.sun.com/javase/6/docs/technotes/tools/share/jmap.html
-XX:-PrintConcurrentLocks 默認不啓用 在線程dump時,順便打印java.util.concurrent鎖狀態。
Jstack –l pid 也同樣實現了相同的功能。詳見 http://java.sun.com/javase/6/docs/technotes/tools/share/jstack.html
-XX:-PrintCommandLineFlags 5.0 引入,默認不啓用 Java啓動時,往stdout打印當前啓用的非穩態jvm options。
例如:
-XX:+UseConcMarkSweepGC -XX:+HeapDumpOnOutOfMemoryError -XX:+DoEscapeAnalysis
-XX:-PrintCompilation 默認不啓用 打印方法被JIT編譯時的信息到stdout。
例如:
java.lang.String::charAt (33 bytes)
-XX:-PrintGC 默認不啓用 開啓GC日誌打印。
例如:
[Full GC 131115K->7482K(1015808K), 0.1633180 secs]
該選項可通過 com.sun.management.HotSpotDiagnosticMXBean API 和 Jconsole 動態啓用。詳見 http://java.sun.com/developer/technicalArticles/J2SE/monitoring/#Heap_Dump
-XX:-PrintGCDetails 1.4.0引入,默認不啓用 打印GC回收的細節。
例如:
[Full GC (System) [Tenured: 0K->2394K(466048K), 0.0624140 secs] 30822K->2394K(518464K), [Perm : 10443K->10443K(16384K)], 0.0625410 secs] [Times: user=0.05 sys=0.01, real=0.06 secs]
該選項可通過 com.sun.management.HotSpotDiagnosticMXBean API 和 Jconsole 動態啓用。詳見 http://java.sun.com/developer/technicalArticles/J2SE/monitoring/#Heap_Dump
-XX:-PrintGCTimeStamps 默認不啓用 打印GC停頓耗時。
例如:
2.744: [Full GC (System) 2.744: [Tenured: 0K->2441K(466048K), 0.0598400 secs] 31754K->2441K(518464K), [Perm : 10717K->10717K(16384K)], 0.0599570 secs] [Times: user=0.06 sys=0.00, real=0.06
secs]
該選項可通過 com.sun.management.HotSpotDiagnosticMXBean API 和 Jconsole 動態啓用。詳見 http://java.sun.com/developer/technicalArticles/J2SE/monitoring/#Heap_Dump
-XX:-PrintTenuringDistribution 默認不啓用 打印對象的存活期限信息。
例如:
[GC
Desired survivor size 4653056 bytes, new threshold 32 (max 32)
- age 1: 2330640 bytes, 2330640 total
- age 2: 9520 bytes, 2340160 total
204009K->21850K(515200K), 0.1563482 secs]
Age1 2表示在第1和2次GC後存活的對象大小。
-XX:-TraceClassLoading 默認不啓用 打印class裝載信息到stdout。記Loaded狀態。
例如:
[Loaded java.lang.Object from /opt/taobao/install/jdk1.6.0_07/jre/lib/rt.jar]
-XX:-TraceClassLoadingPreorder 1.4.2引入,默認不啓用 按class的引用/依賴順序打印類裝載信息到stdout。不同於 TraceClassLoading,本選項只記 Loading狀態。
例如:
[Loading java.lang.Object from /home/confsrv/jdk1.6.0_14/jre/lib/rt.jar]
-XX:-TraceClassResolution 1.4.2引入,默認不啓用 打印所有靜態類,常量的代碼引用位置。用於debug。
例如:
RESOLVE java.util.HashMap java.util.HashMap$Entry HashMap.java:209
說明HashMap類的209行引用了靜態類 java.util.HashMap$Entry
-XX:-TraceClassUnloading 默認不啓用 打印class的卸載信息到stdout。記Unloaded狀態。
-XX:-TraceLoaderConstraints Java6 引入,默認不啓用 打印class的裝載策略變化信息到stdout。
例如:
[Adding new constraint for name: java/lang/String, loader[0]: sun/misc/Launcher$ExtClassLoader, loader[1]: <bootloader> ]
[Setting class object in existing constraint for name: [Ljava/lang/Object; and loader sun/misc/Launcher$ExtClassLoader ]
[Updating constraint for name org/xml/sax/InputSource, loader <bootloader>, by setting class object ]
[Extending constraint for name java/lang/Object by adding loader[15]: sun/reflect/DelegatingClassLoader  ]
裝載策略變化是實現classloader隔離/名稱空間一致性的關鍵技術。
-XX:+PerfSaveDataToFile 默認啓用 當java進程因OOM或crash被強制退出後,生成一個堆快照文件(什麼是堆內存快照? 見 -XX:HeapDumpPath 處的描述)。

 

四、  經驗

     1.年輕代大小選擇

         1)響應時間優先的應用:儘可能設大,直到接近系統的最低響應時間限制(根據實際情況選擇).在此種情況下,年輕代收集發生的頻率也是最小的.同時,減少到達年老代的對象.

         2)吞吐量優先的應用:儘可能的設置大,可能到達Gbit的程度.因爲對響應時間沒有要求,垃圾收集可以並行進行,一般適合8CPU以上的應用.

         3)避免設置過小.當新生代設置過小時會導致:1.YGC次數更加頻繁 2.可能導致YGC對象直接進入舊生代,如果此時舊生代滿了,會觸發FGC.

     2.年老代大小選擇

         1)響應時間優先的應用:年老代使用併發收集器,所以其大小需要小心設置,一般要考慮併發會話率和會話持續時間等一些參數.如果堆設置小了,可以會造成內存碎 片,高回收頻率以及應用暫停而使用傳統的標記清除方式;如果堆大了,則需要較長的收集時間.最優化的方案,一般需要參考以下數據獲得: 併發垃圾收集信息、持久代併發收集次數、傳統GC信息、花在年輕代和年老代回收上的時間比例。

         2)吞吐量優先的應用:一般吞吐量優先的應用都有一個很大的年輕代和一個較小的年老代.原因是,這樣可以儘可能回收掉大部分短期對象,減少中期的對象,而年老代盡存放長期存活對象.

     3.較小堆引起的碎片問題:promotion failed

         1)因爲年老代的併發收集器使用標記,清除算法,所以不會對堆進行壓縮.當收集器回收時,他會把相鄰的空間進行合併,這樣可以分配給較大的對象.但是,當堆空間較小時,運行一段時間以後,就會出現"碎片",如果併發收集器找不到足夠的空間,那麼併發收集器將會停止,然後使用傳統的標記,清除方式進行回收.如果出現"碎片",可能需要進行如下配置:

-XX:+UseCMSCompactAtFullCollection:使用併發收集器時,開啓對年老代的壓縮.

-XX:CMSFullGCsBeforeCompaction=0:上面配置開啓的情況下,這裏設置多少次Full GC後,對年老代進行壓縮

     4.用64位操作系統,Linux下64位的jdk比32位jdk要慢一些,但是吃得內存更多,吞吐量更大

     5.XMX和XMS設置一樣大,MaxPermSize和MinPermSize設置一樣大,這樣可以減輕伸縮堆大小帶來的壓力

     6.使用CMS的好處是用盡量少的新生代,經驗值是128M-256M, 然後老生代利用CMS並行收集, 這樣能保證系統低延遲的吞吐效率。 實際上cms的收集停頓時間非常的短,2G的內存, 大約20-80ms的應用程序停頓時間

     7.系統停頓的時候可能是GC的問題也可能是程序的問題,多用jmap和jstack查看,或者killall -3 java,然後查看java控制檯日誌,能看出很多問題。(相關工具的使用方法將在後面的blog中介紹)

     8.增加Heap的大小雖然會降低GC的頻率,但也增加了每次GC的時間。並且GC運行時,所有的用戶線程將暫停,也就是GC期間,Java應用程序不做任何工作。

     9.Heap大小並不決定進程的內存使用量。進程的內存使用量要大於-Xmx定義的值,因爲Java爲其他任務分配內存,例如每個線程的Stack等。

每個線程都有他自己的Stack。-Xss

Stack的大小限制着線程的數量。如果Stack過大就好導致內存溢漏。-Xss參數決定Stack大小,例如-Xss1024K。如果Stack太小,也會導致Stack溢漏。

     10.Java線程的內存是位於JVM或操作系統的棧(Stack)空間中,不同於對象——是位於堆(Heap)中。這是很多新手程序員容易誤解的地方。注意,“Java線程的內存”這個用詞不是指Java.lang.Thread對象的內存,java.lang.Thread對象本身是在Heap中分配的,當調用start()方法之後,JVM會創建一個執行單元,最終會創建一個操作系統的native thread來執行,而這個執行單元或native thread是使用Stack內存空間的。

     11.VM進程的內存大致分爲Heap空間和Stack空間兩部分。Heap又分爲Young、Old、Permanent三個代。Stack分爲Java方法棧和native方法棧(不做區分),在Stack內存區中,可以創建多個線程棧,每個線程棧佔據Stack區中一小部分內存,線程棧是一個LIFO數據結構,每調用一個方法,會在棧頂創建一個Frame,方法返回時,相應的Frame會從棧頂移除(通過移動棧頂指針)。在這每一部分內存中,都有可能會出現溢出錯誤

     12.仔細瞭解自己的應用,如果用了緩存,那麼年老代應該大一些,緩存的HashMap不應該無限制長,建議採用LRU算法的Map做緩存,LRUMap的最大長度也要根據實際情況設定。

     13.採用併發回收時,年輕代小一點,年老代要大,因爲年老大用的是併發回收,即使時間長點也不會影響其他程序繼續運行,網站不會停頓

     14.JVM參數的設置(特別是 –Xmx –Xms –Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold等參數的設置沒有一個固定的公式,需要根據PV old區實際數據 YGC次數等多方面來衡量。爲了避免promotion faild可能會導致xmn設置偏小,也意味着YGC的次數會增多,處理併發訪問的能力下降等問題。每個參數的調整都需要經過詳細的性能測試,才能找到特定應用的最佳配置。

五、  常見內存錯誤及解決方案

     1.OutOfMemoryError在開發過程中是司空見慣的,遇到這個錯誤,新手程序員都知道從兩個方面入手來解決:一是排查程序是否有BUG導致內存泄漏;二是調整JVM啓動參數增大內存。OutOfMemoryError有好幾種情況,每次遇到這個錯誤時,觀察OutOfMemoryError後面的提示信息,就可以發現不同之處,如:

 

java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: unable to create newnative thread
java.lang.OutOfMemoryError: PermGen space
java.lang.OutOfMemoryError: Requested array sizeexceeds VM limit

 

     2.java.lang.OutOfMemoryError:Java heap space

         1)原因:Heap內存溢出,意味着Young和Old generation的內存不夠。

         2)解決:調整java啓動參數 -Xms -Xmx 來增加Heap內存。

     3.java.lang.OutOfMemoryError:unable to create new native thread

         1)原因:Stack空間不足以創建額外的線程,要麼是創建的線程過多,要麼是Stack空間確實小了。

         2)解決:由於JVM沒有提供參數設置總的stack空間大小,但可以設置單個線程棧的大小;而系統的用戶空間一共是3G,除了Text/Data/BSS/MemoryMapping幾個段之外,Heap和Stack空間的總量有限,是此消彼長的。因此遇到這個錯誤,可以通過兩個途徑解決:1.通過-Xss啓動參數減少單個線程棧大小,這樣便能開更多線程(當然不能太小,太小會出現StackOverflowError);2.通過-Xms -Xmx 兩參數減少Heap大小,將內存讓給Stack(前提是保證Heap空間夠用)。

     4.java.lang.OutOfMemoryError:PermGen space

         1)原因:Permanent Generation空間不足,不能加載額外的類。

         2)解決:調整-XX:PermSize= -XX:MaxPermSize= 兩個參數來增大PermGen內存。一般情況下,這兩個參數不要手動設置,只要設置-Xmx足夠大即可,JVM會自行選擇合適的PermGen大小。

     5.java.lang.OutOfMemoryError:Requested array size exceeds VM limit

         1)原因:這個錯誤比較少見(試着new一個長度1億的數組看看),同樣是由於Heap空間不足。如果需要new一個如此之大的數組,程序邏輯多半是不合理的。

         2)解決:修改程序邏輯吧。或者也可以通過-Xmx來增大堆內存。

     6.java.lang.OutOfMemoryError: GC overhead limit exceeded

         1)原因:在GC花費了大量時間,卻僅回收了少量內存時,也會報出OutOfMemoryError,我只遇到過一兩次。當使用-XX:+UseParallelGC或-XX:+UseConcMarkSweepGC收集器時,在上述情況下會報錯,在HotSpot GC Turning文檔上有說明:

The parallel(concurrent) collector will throwan OutOfMemoryError if too much time is being spent in garbage collection: ifmore than 98% of the total time is spent in garbage collection and less than 2%of the heap is recovered, an OutOfMemoryError will be thrown.

對這個問題,一是需要進行GC turning,二是需要優化程序邏輯。

     7.java.lang.StackOverflowError

         1)原因:這也內存溢出錯誤的一種,即線程棧的溢出,要麼是方法調用層次過多(比如存在無限遞歸調用),要麼是線程棧太小。

         2)解決:優化程序設計,減少方法調用層次;調整-Xss參數增加線程棧大小。

     8.concurrentmode failure

         1)原因:原文是這樣描述的(if theconcurrent collector is unable tofinish reclaiming the unreachable objectsbefore the tenured generation fillsup, or if an allocation cannot be satisfiedwith the available free space blocksin the tenured generation, then theapplication is paused and the collection iscompleted with all the applicationthreads stopped),簡單解釋就是old gen剩餘的內存不足以滿足來自於young gen的垃圾回收,導致jvm通過卸載已經生成的反射類來釋放足夠的內存。這種現象會造成應用較長時間的中斷,從而影響性能。所以理論上應該保證eden + from survivor < old gen區剩餘內存。

         2)通過設置-XX:CMSInitiatingOccupancyFraction=50(此值的初始值爲10,即預留10%的空間給minor collection),當old gen已經收集了50%的內存後,就開始進行major collection,從而保證old gen 始終預留50%的可用內存。

         3)這種設置雖然會提高major collection的頻率,但是根據目前major collection的頻率來看(大概幾個小時一次)足以承受。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章