JVM內存結構

java 虛擬機有本身完善的硬體架構,如處理器、堆棧、寄存器等,還具備相應的指令系統
 
 
JVM Heap 分兩大塊:  一塊是 NEW Generation,另外一塊是 Old Generation.
在 NewGeneration 中,有一個叫 Eden 的空間,主要是用來存放新生的對象,還有兩個 Survivor Spaces(from,to),它們的大小老是同樣,它們用來存放每次垃圾回收後存活下來的對象。
在 OldGeneration 中,主要存放應用程序中生命週期長的內存對象。
在NewGeneration 塊中,垃圾回收通常用 Copying 的算法,速度快。每次 GC 的時候,存活下來的對象首先由 Eden 拷貝到某個 SurvivorSpace, 當 Survivor Space 空間滿了後, 剩下的 live 對象就被直接拷貝到OldGeneration 中去。所以,每次 GC 後,Eden 內存塊會被清空。
在 OldGeneration 塊中,垃圾回收通常用 mark-compact 的算法,速度慢些,但減小內存要求.
 
垃圾回收分多級:
Full GC:會回收 OLD 和 Perm中的垃圾;
Major GC:會回收OLD 中的垃圾;
Minor GC:只回收 NEW 中的垃圾;
內存溢出一般發生於 OLD 段或 Perm 段垃圾回收後,仍然無內存空間容納新的 Java對象的狀況。 
 
內存申請過程以下:
1. JVM 會試圖爲相關 Java 對象在 Eden 中初始化一塊內存區域
2. 當 Eden 空間足夠時,內存申請結束。不然到下一步
3. JVM 試圖釋放在 Eden 中全部不活躍的對象(這屬於minor級別垃圾回收),釋放後若 Eden 空間仍然不足以放入新對象,則試圖將部分 Eden 中活躍對象放入 Survivor 區
4. Survivor 區被用來做爲 Eden 及 OLD 的中間交換區域,當to空間放不下eden和from的對象,且OLD 區空間擔保成功時,from區的對象會被移到 Old 區,不然會被移到to 區
5. 當 OLD 區空間不夠時,JVM 會在 OLD 區進行full GC級
6. 徹底垃圾收集後,若 to 及 OLD 區仍然沒法存放從 Eden 複製過來的部分對象,致使 JVM沒法在 Eden 區爲新對象建立內存區域,則出現」out of memory 錯誤」
 
hotspot jvm結構以下(虛擬機棧和本地方法棧合一塊兒了):

 

JDK8 永久代變化以下圖:java

1.新生代:Eden+From Survivor+To Survivor程序員

2.老年代:OldGen算法

3.永久代(方法區的實現) : PermGen----->替換爲Metaspace(本地內存中)架構

爲何廢棄永久代:因爲永久代內存常常不夠用或發生內存泄露,爆出異常java.lang.OutOfMemoryError: PermGenjvm

深刻理解元空間(Metaspace):元空間的內存大小:元空間並不在虛擬機中,而是使用本地內存。理論上取決於32位/64位系統可虛擬的內存大小。可見也不是無限制的,須要配置參數。性能

在jdk8中:ui

1.字符串常量由永久代轉移到堆中。spa

2.持久代已不存在,PermSize MaxPermSize參數已移除。操作系統

3.類加載(方法區的功能)已經不在永久代PerGem中了,而是Metaspace中線程

  
VM 調優建議:
在用戶生產環境上通常將如下三組值設爲相同,以減小運行期間系統在內存申請上所花的開銷。
1.ms/mx:定義 YOUNG+OLD 段的總尺寸,ms 爲 JVM 啓動時 YOUNG+OLD 的內存大小;mx 爲最大可佔用的 YOUNG+OLD 內存大小。在用戶生產環境上通常將這兩個值設爲相同,以減小運行期間系統在內存申請上所花的開銷。(進程啓動時申請ms內存,當運行過程當中須要的內容超過ms時,進程須要掛起來等待申請到更新內存時才能執行。爲了不這種狀況發生,最好定義ms和mx相同)
2.NewSize/MaxNewSize:定義 YOUNG 段的尺寸,NewSize 爲 JVM 啓動時 YOUNG 的內存大小;MaxNewSize 爲最大可佔用的 YOUNG 內存大小。
3.因爲heap大小值相同,Yound大小值相同、因此剩下的Old大小值應該也相同
 
 
1. OLD 段溢出
這種內存溢出是最多見的狀況之一,產生的緣由多是:
1) 設置的內存參數太小(ms/mx, NewSize/MaxNewSize)
2) 程序問題
單個程序持續進行消耗內存的處理,如循環幾千次的字符串處理,對字符串處理應建議使用 StringBuilder。此時不會報內存溢出錯,卻會使系統持續垃圾收集,沒法處理其它請求,相關問題程序可經過 Thread Dump獲取單個程序所申請內存過大,有的程序會申請幾十乃至幾百兆內存,此時 JVM也會因沒法申請到資源而出現內存溢出,對此首先要找到相關功能,而後交予程序員修改,要找到相關程序,必須在 Apache 日誌中尋找。當 Java 對象使用完畢後,其所引用的對象卻沒有銷燬,使得 JVM 認爲他仍是活躍的對象而不進行回收,這樣累計佔用了大量內存而沒法釋放。
2. Perm 段溢出
一般因爲 Perm 段裝載了大量的 Servlet 類而致使溢出,目前的解決辦法:
1) 將 PermSize 擴大,通常 256M 可以知足要求
2) 若別無選擇,則只能將 servlet 的路徑加到 CLASSPATH 中,但通常不建議這麼處理
 
 
3.  Heap 溢出
系統對 Heap 沒有限制,故  Heap 發生問題時,Java 進程所佔內存會持續增加,直到佔用全部可用系統內存
 
4.其餘:
JVM 有 2 個 GC 線程。第一個線程負責回收 Heap 的 Young 區。第二個線程在 Heap 不足時,遍歷 Heap,將 Young 區升級爲 Older 區。Older 區的大小等於-Xmx 減去-Xmn,不能將-Xms 的值設的過大,由於第二個線程被迫運行會下降 JVM 的性能。
 
爲何一些程序頻繁發生 GC?有以下緣由:
 程序內調用了 System.gc()或 Runtime.gc()。 一些中間件軟件調用本身的 GC 方法,此時須要設置參數禁止這些 GC。
 Java 的 Heap 過小,通常默認的 Heap 值都很小。
 頻繁實例化對象,Release 對象。此時儘可能保存並重用對象,例如使用 StringBuffer()和 String()。
若是你發現每次 GC 後,Heap 的剩餘空間會是總空間的 50%,這表示你的 Heap 處於健康狀態。許多 Server端的 Java 程序每次 GC 後最好能有 65%的剩餘空間。
 
典型設置:
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
    -Xmx3550m:設置JVM最大可用內存爲3550M。
    -Xms3550m:設置JVM初始內存爲3550m。此值能夠設置與-Xmx相同,以免每次垃圾回收完成後JVM從新分配內存。
    -Xmn2g:設置年輕代大小爲2G。整個JVM內存大小=年輕代大小 + 年老代大小 + 持久代大小(jdk8之後廢棄)。
    -Xss128k:設置每一個線程的堆棧大小。JDK5.0之後每一個線程堆棧大小爲1M,之前每一個線程堆棧大小爲256K。在相同物理內存下,減少這個值能生成更多的線程。可是操做系統對一個進程內的線程數仍是有限制的,不能無限生成,經驗值在3000~5000左右。
  • java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
    -XX:NewRatio=4:設置年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。設置爲4,則年輕代與年老代所佔比值爲1:4,年輕代佔整個堆棧的1/5
    -XX:SurvivorRatio=4:設置年輕代中Eden區與Survivor區的大小比值。設置爲4,則兩個Survivor區與一個Eden區的比值爲2:4,一個Survivor區佔整個年輕代的1/6
2.增長 Heap 的大小雖然會下降 GC 的頻率,但也增長了每次 GC 的時間。而且 GC 運行時,全部的用戶線程將暫停,也就是 GC 期間,Java 應用程序不作任何工做。
4.Heap 大小並不決定進程的內存使用量。進程的內存使用量要大於-Xmx 定義的值,由於 Java 爲其餘任務分配內存,例如每一個線程的 Stack 等。
5 .  ystem.gc()顯示調用會觸發 Full GC。對整個堆進行整理,包括 Young、Tenured 和 Perm。要儘可能避免。
 
進一步調優總結,可參考 http://unixboy.iteye.com/blog/174173/
相關文章
相關標籤/搜索