標籤:.net 導致 reference 行合并 nbsp 最大 存在 類型 free
轉自:http://blog.csdn.net/heyutao007/article/details/38151581
1、JVM記憶體組成結構
JVM記憶體結構由堆、棧、本地方法棧、方法區等部分組成,結構圖如下所示:
1)堆
所有通過new建立的對象的記憶體都在堆中分配,其大小可以通過-Xmx和-Xms來控制。堆被劃分為新生代和舊生代,新生代又被進一步劃分為Eden和Survivor區,Survivor由FromSpace和ToSpace組成,結構圖如下所示:
新生代。建立的對象都是用新生代分配記憶體,Eden空間不足的時候,會把存活的對象轉移到Survivor中,新生代大小可以由-Xmn來控制,也可以用-XX:SurvivorRatio來控制Eden和Survivor的比例。
舊生代用於存放新生代中經過多次記憶體回收(也即Minor GC)仍然存活的對象
2)棧
每個線程執行每個方法的時候都會在棧中申請一個棧幀,每個棧幀包括局部變數區和運算元棧,用於存放此次方法調用過程中的臨時變數、參數和中間結果
3)本地方法棧
用於支援native方法的執行,儲存了每個native方法調用的狀態
4)方法區
存放了要載入的類資訊、靜態變數、final類型的常量、屬性和方法資訊。JVM用持久代(或者叫做永久代,PermanetGeneration)來存放方法區,可通過-XX:PermSize和-XX:MaxPermSize來指定最小值和最大值。
在過去(自訂類載入器還不是很常見的時候),類大多是”static”的,很少被卸載或收集,因此被稱為“永久的(Permanent)”。同時,由於類class是JVM實現的一部分,並不是由應用建立的,所以又被認為是“非堆(non-heap)”記憶體。
在JDK8之前的HotSpot JVM,存放這些”永久的”的地區叫做“永久代(permanent generation)”。永久代是一片連續的堆空間,在JVM啟動之前通過在命令列設定參數-XX:MaxPermSize來設定永久代最大可分配的記憶體空間,預設大小是64M(64位JVM由於指標膨脹,預設是85M)。永久代的垃圾收集是和老年代(old generation)捆綁在一起的,因此無論誰滿了,都會觸發永久代和老年代的垃圾收集。不過,一個明顯的問題是,當JVM載入的類資訊容量超過了參數-XX:MaxPermSize設定的值時,應用將會報OOM的錯誤。
永久代在JDK8中被完全的移除了。所以永久代的參數-XX:PermSize和-XX:MaxPermSize也被移除了。
在JDK8中,classe metadata(the virtual machines internal presentation of Java class),被儲存在叫做Metaspace的native memory。一些新的flags被加入:
-XX:MetaspaceSize,class metadata的初始空間配額,以bytes為單位,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:如果釋放了大量的空間,就適當的降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize(如果設定了的話),適當的提高該值。
-XX:MaxMetaspaceSize,可以為class metadata分配的最大空間。預設是沒有限制的。
-XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少為class metadata分配空間導致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少為class metadata釋放空間導致的垃圾收集
預設情況下,class metadata的分配僅受限於可用的native memory總量。可以使用MaxMetaspaceSize來限制可為class metadata分配的最大記憶體。當class metadata的使用的記憶體達到MetaspaceSize(32位clientVM預設12Mbytes,32位ServerVM預設是16Mbytes)時就會對死亡的類載入器和類進行垃圾收集。設定MetaspaceSize為一個較高的值可以延遲垃圾收集的發生。
介紹完了JVM記憶體組成結構,下面我們再來看一下JVM記憶體回收機制。
2、java GC基本演算法
2.1、引用計數(reference counting)
原理:此對象有一個引用,則+1;刪除一個引用,則-1。只用收集計數為0的對象。
缺點: (1)無法處理循環參考的問題。如:對象A和B分別有欄位b、a,令A.b=B和B.a=A,除此之外這2個對象再無任何引用,那實際上這2個對象已經不可能再被訪問,但是引用計數演算法卻無法回收他們。(2)引用計數的方法需要編譯器的配合。編譯器需要為此對象產生額外的代碼。如賦值函數將此對象賦值給一個引用時,需要增加此對象的引用計數。還有就是,當一個引用變數的生命週期結束時,需要更新此對象的引用計數器。
引用計數的方法由於存在顯著的缺點,實際上並未被JVM所使用。
2.2、複製(copying)
原理:把記憶體空間劃分為2個相等的地區,每次只使用一個地區。記憶體回收時,遍曆當前使用地區,把正在使用的對象複製到另外一個地區。
優點:不會出現片段問題。
缺點:
(1)暫停整個應用。
(2)需要2倍的記憶體空間。
2.3、標記-清掃(Mark-and-sweep)
原理:對於“活”的對象,一定可以追溯到其存活在堆棧、靜態儲存區之中的引用。這個引用鏈條可能會穿過數個對象層次,演算法基於有向圖,採用深度優先搜尋。
第一階段:從GC roots開始遍曆所有的引用,對有活的對象進行標記。
第二階段:對堆進行遍曆,把未標記的對象進行清除。
優點:解決了循環參考的問題。
缺點:
(1)暫停整個應用;
(2)會產生記憶體片段。
(3)不管你這個對象是不是可達的,即是不是垃圾,都要在清楚階段被檢查一遍,非常耗時.
sun前期版本就是用這個技術。
2.4、標記-壓縮(Mark-Compact)
第一階段:同標記-清掃,標記活的對象,
第二階段:這個階段將所有做了標記的使用中的物件整理到堆的底部
優點:
(1)避免標記掃描的片段問題;
(2)避免停止複製的空間問題。
2.5、分代(generational collecting)
原理:基於對象生命週期分析得出的記憶體回收演算法。把對象分為年輕代、年老代、持久代,對不同的生命週期使用不同的演算法(2-3方法中的一個即4自適應)進行回收。
J2SE1.2以後使用此演算法
JVM分別對新生代和舊生代採用不同的記憶體回收機制
2.5.1、新生代的GC(Minor GC):
指發生在新生代的垃圾收集動作,因為 Java 對象大多都具備朝生夕滅的特性,所以 Minor GC 非常頻繁,一般回收速度也比較快。
新生代通常存活時間較短,因此基於Copying演算法來進行回收,所謂Copying演算法就是掃描出存活的對象,並複製到一塊新的完全未使用的空間中,對應於新生代,就是在Eden和FromSpace或ToSpace之間copy。新生代採用空閑指標的方式來控制GC觸發,指標保持最後一個分配的對象在新生代區間的位置,當有新的對象要分配記憶體時,用於檢查空間是否足夠,不夠就觸發GC。當連續指派至時,對象會逐漸從eden到survivor,最後到舊生代,
2.5.2、舊生代的GC(Major GC / Full GC):
指發生在老年代的 GC。舊生代與新生代不同,對象存活的時間比較長,比較穩定,因此採用標記(Mark)演算法來進行回收,所謂標記就是掃描出存活的對象,然後再進行回收未被標記的對象,回收後對用空出的空間要麼進行合并,要麼標記出來便於下次進行分配,總之就是要減少記憶體片段帶來的效率損耗。
MajorGC 的速度一般會比 Minor GC 慢 10倍以上。
Thinking in java給java gc取了一個羅嗦的稱呼:“自適應、分代的、停止-複製、標記-掃描”式的記憶體回收行程。
導致Gc的情況:
1、tenured被寫滿
2、perm被寫滿
3、System.gc()的顯式調用。
4、上一次GC之後heap的各域分配策略動態變化。
Java GC演算法