[转]JVM运行时内存结构
[轉]http://www.cnblogs.com/dolphin0520/p/3783345.html
目錄[-]
- 1.為什么會有年輕代
- 2.年輕代中的GC
- 3.一個對象的這一輩子
- 4.有關年輕代的JVM參數
1.為什么會有年輕代
? ? ?我們先來屢屢,為什么需要把堆分代?不分代不能完成他所做的事情么?其實不分代完全可以,分代的唯一理由就是優化GC性能。你先想想,如果沒有分代,那我們所有的對象都在一塊,GC的時候我們要找到哪些對象沒用,這樣就會對堆的所有區域進行掃描。而我們的很多對象都是朝生夕死的,如果分代的話,我們把新創建的對象放到某一地方,當GC的時候先把這塊存“朝生夕死”對象的區域進行回收,這樣就會騰出很大的空間出來。
2.年輕代中的GC
??? HotSpot JVM把年輕代分為了三部分:1個Eden區和2個Survivor區(分別叫from和to)。默認比例為8:1,為啥默認會是這個比例,接下來我們會聊到。一般情況下,新創建的對象都會被分配到Eden區(一些大對象特殊處理),這些對象經過第一次Minor GC后,如果仍然存活,將會被移到Survivor區。對象在Survivor區中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度時,就會被移動到年老代中。
? ? 因為年輕代中的對象基本都是朝生夕死的(80%以上),所以在年輕代的垃圾回收算法使用的是復制算法,復制算法的基本思想就是將內存分為兩塊,每次只用其中一塊,當這一塊內存用完,就將還活著的對象復制到另外一塊上面。復制算法不會產生內存碎片。
? ? 在GC開始的時候,對象只會存在于Eden區和名為“From”的Survivor區,Survivor區“To”是空的。緊接著進行GC,Eden區中所有存活的對象都會被復制到“To”,而在“From”區中,仍存活的對象會根據他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被復制到“To”區域。經過這次GC后,Eden區和From區已經被清空。這個時候,“From”和“To”會交換他們的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名為To的Survivor區域是空的。Minor GC會一直重復這樣的過程,直到“To”區被填滿,“To”區被填滿之后,會將所有對象移動到年老代中。
3.一個對象的這一輩子
? ? 我是一個普通的java對象,我出生在Eden區,在Eden區我還看到和我長的很像的小兄弟,我們在Eden區中玩了挺長時間。有一天Eden區中的人實在是太多了,我就被迫去了Survivor區的“From”區,自從去了Survivor區,我就開始漂了,有時候在Survivor的“From”區,有時候在Survivor的“To”區,居無定所。直到我18歲的時候,爸爸說我成人了,該去社會上闖闖了。于是我就去了年老代那邊,年老代里,人很多,并且年齡都挺大的,我在這里也認識了很多人。在年老代里,我生活了20年(每次GC加一歲),然后被回收。
4.有關年輕代的JVM參數
1)-XX:NewSize和-XX:MaxNewSize
? ?用于設置年輕代的大小,建議設為整個堆大小的1/3或者1/4,兩個值設為一樣大。
2)-XX:SurvivorRatio
? ?用于設置Eden和其中一個Survivor的比值,這個值也比較重要。
3)-XX:+PrintTenuringDistribution
? ?這個參數用于顯示每次Minor GC時Survivor區中各個年齡段的對象的大小。
4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold
? ?用于設置晉升到老年代的對象年齡的最小值和最大值,每個對象在堅持過一次Minor GC之后,年齡就加1。
?--------------------------------------------------------------------------------------------------------------------------
http://my.oschina.net/sunchp/blog/369707?p=1#comments
1.JVM內存模型
JVM運行時內存=共享內存區+線程內存區
1).共享內存區
共享內存區=持久帶+堆
持久帶=方法區+其他
堆=Old Space+Young Space
Young Space=Eden+S0+S1
(1)持久帶
JVM用持久帶(Permanent Space)實現方法區,主要存放所有已加載的類信息,方法信息,常量池等等。
可通過-XX:PermSize和-XX:MaxPermSize來指定持久帶初始化值和最大值。
Permanent Space并不等同于方法區,只不過是Hotspot JVM用Permanent Space來實現方法區而已,有些虛擬機沒有Permanent Space而用其他機制來實現方法區。
(2)堆
堆,主要用來存放類的對象實例信息。
堆分為Old Space(又名,Tenured Generation)和Young Space。
Old Space主要存放應用程序中生命周期長的存活對象;
Eden(伊甸園)主要存放新生的對象;
S0和S1是兩個大小相同的內存區域,主要存放每次垃圾回收后Eden存活的對象,作為對象從Eden過渡到Old Space的緩沖地帶(S是指英文單詞Survivor Space)。
堆之所以要劃分區間,是為了方便對象創建和垃圾回收,后面垃圾回收部分會解釋。
2).線程內存區
線程內存區=單個線程內存+單個線程內存+.......
單個線程內存=PC Regster+JVM棧+本地方法棧
JVM棧=棧幀+棧幀+.....
棧幀=局域變量區+操作數區+幀數據區
在Java中,一個線程會對應一個JVM棧(JVM Stack),JVM棧里記錄了線程的運行狀態。
JVM棧以棧幀為單位組成,一個棧幀代表一個方法調用。棧幀由三部分組成:局部變量區、操作數棧、幀數據區。
(1)局部變量區
局部變量區,可以理解為一個以數組形式進行管理的內存區,從0開始計數,每個局部變量的空間是32位的,即4字節。
基本類型byte、char、short、boolean、int、float及對象引用等占一個局部變量空間,類型為short、byte和char的值在存入數組前要被轉換成int值;long、double占兩個局部變量空間,在訪問long和double類型的局部變量時,只需要取第一個變量空間的索引即可,。
例如:
?| 1 2 3 4 5 6 7 | public?static?int?runClassMethod(int?i,long?l,float?f,double?d,Object?o,byte?b)?{??? ????return?0;??? }??? ???????????? public?int?runInstanceMethod(char?c,double?d,short?s,boolean?b)?{??? ????return?0;??? } |
對應的局域變量區是:
runInstanceMethod的局部變量區第一項是個reference(引用),它指定的就是對象本身的引用,也就是我們常用的this,但是在runClassMethod方法中,沒這個引用,那是因為runClassMethod是個靜態方法。
(2)操作數棧
操作數棧和局部變量區一樣,也被組織成一個以字長為單位的數組。但和前者不同的是,它不是通過索引來訪問的,而是通過入棧和出棧來訪問的。操作數棧是臨時數據的存儲區域。
例如:
?| 1 2 3 | int?a=?100; int?b?=5; int?c?=?a+b; |
對應的操作數棧變化為:
從圖中可以得出:操作數棧其實就是個臨時數據存儲區域,它是通過入棧和出棧來進行操作的。
PS:JVM實現里,有一種基于棧的指令集(Hotspot,oracle JVM),還有一種基于寄存器的指令集(DalvikVM,安卓 JVM),兩者有什么區別的呢?
基于棧的指令集有接入簡單、硬件無關性、代碼緊湊、棧上分配無需考慮物理的空間分配等優勢,但是由于相同的操作需要更多的出入棧操作,因此消耗的內存更大。 而基于寄存器的指令集最大的好處就是指令少,速度快,但是操作相對繁瑣。
示例:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package?com.demo3; public?class?Test?{ ????public?static?void?foo()?{ ????????int?a?=?1; ????????int?b?=?2; ????????int?c?=?(a?+?b)?*?5; ????} ????public?static?void?main(String[]?args)?{ ????????foo(); ????} } |
基于棧的Hotspot的執行過程如下:
基于寄存器的DalvikVM執行過程如下所示:
上述兩種方式最終通過JVM執行引擎,CPU接收到的匯編指令是:
(3)幀數據區
?幀數據區存放了指向常量池的指針地址,當某些指令需要獲得常量池的數據時,通過幀數據區中的指針地址來訪問常量池的數據。此外,幀數據區還存放方法正常返回和異常終止需要的一些數據。
2.垃圾回收機制
1)、為什么要垃圾回收
JVM自動檢測和釋放不再使用的內存,提高內存利用率。?
Java 運行時JVM會執行 GC,這樣程序員不再需要顯式釋放對象。?
2)、回收哪些內存區域
因為線程內存區隨著線程的產生和退出而分配和回收,所以垃圾回收主要集中在共享內存區,也就是持久帶(Permanent Space)和堆(Heap)。
3)、如何判斷對象已死 (對象標記)
(1)引用計數法
引用計數法就是通過一個計數器記錄該對象被引用的次數,方法簡單高效,但是解決不了循環引用的問題。比如對象A包含指向對象B的引用,對象B也包含指向對象A的引用,但沒有引用指向A和B,這時當前回收如果采用的是引用計數法,那么對象A和B的被引用次數都為1,都不會被回收。JVM不是采用這種方法。
(2)?根搜索(可達性分析算法)
根搜索(可達性分析算法)可以解決對象循環引用的問題,基本原理是:通過一個叫“GC ROOT”根對象作為起點,然后根據關聯關系,向下節點搜索,搜索路徑叫引用鏈,也就是常說的引用。從“GC ROOT”根對象找不到任何一條路徑與之相連的對象,就被判定可以回收,相當于這對象找不到家的感覺。
示例圖:
GC會收集那些不是GC root且沒有被GC root引用的對象。一個對象可以屬于多個GC root。
GC root有幾下種:
-
虛擬機棧(棧幀中的本地變量表)中引用的對象
-
方法區中類靜態屬性引用的對象
-
方法區中常量引用的對象
-
本地方法棧中JNI(native方法)引用的對象
-
用于JVM特殊目的對象,例如系統類加載器等等
雖然有可達性分析算法來判定對象狀態,但這并不是對象是否被回收的條件,對象回收的條件遠遠比這個復雜。無法通過GC ROOT關聯到的對象,不都是立刻被回收。如果這個對象沒有被關聯到,而且沒有被mark2標記,那么會進入一個死緩的階段,被第一次標記(mark1),然后被放入一個F-Queue隊列;如果這個對象被mark2標記了,那么這個對象將會被回收。
F-Queue隊列由一個優先級較低的Finalizer線程去執行,其中的mark1對象等待執行自己的finalize()方法(JVM并不保證等待finalize()方法運行結束,因為finalize() 方法或者執行慢,或者死循環,會影響該隊列其他元素執行)。執行mark1對象的finalize()方法,就會進行第二次標記(mark2)。以后的GC都會按這個邏輯執行“搜索,標記1,標記2”。
這一“標記”過程是后續垃圾回收算法的基礎。
PS:
-
如果在finalize() 方法體內,再次對本對象進行引用,那么對象就復活了。
-
finalize()方法只會被執行一次,所以對象只有一次復活的機會。
3)垃圾回收算法
垃圾回收算法主要有三種:
-
標記-清除
-
標記-復制
-
標記-整理
這三種都有“標記”過程,這個標記過程就是上述的根搜索(可達性分析算法)。后面的“清除”、“復制”和“整理”動作,是具體的對象被回收的實現方式。
(1)標記-清除
通過根搜索(可達性分析算法)標記完成后,直接將標記為垃圾的對象所占內存空間釋放。這種算法的缺點是內存碎片多。
雖然缺點明顯,這種策略卻是后兩種策略的基礎。正因為它的缺點,所以促成了后兩種策略的產生。
動圖:
(2)標記-復制
通過根搜索(可達性分析算法)標記完成后,將內存分為兩塊,將一塊內存中保留的對象全部復制到另
一塊空閑內存中。
動圖:
這種算法的缺點是,可用內存變成了一半。怎么解決這個缺點呢?
JVM將堆(heap)分成young區和old區。young區包括eden、s0、s1,并且三個區之間的大小有一定比例。例如,按8:1:1分成一塊Eden和兩小塊Survivor區,每次GC時,young區里,將Eden和S0中存活的對象復制到另一塊空閑的S1中。
young區的垃圾回收是經常要發生的,被稱為Minor GC(次要回收)。一般情況下,當新對象生成,并且在Eden申請空間失敗時,就會觸發Minor GC,對Eden區域進行GC,清除非存活對象,并且把尚且存活的對象移動到Survivor區。然后整理Survivor的兩個區。這種方式的GC是對Young space的Eden區進行,不會影響到Old space。因為大部分對象都是從Eden區開始的,同時Eden區不會分配的很大,所以Eden區的GC會頻繁進行。因而,一般在這里需要使用速度快、效率高的算法,使Eden去能盡快空閑出來。
Minor GC主要過程:
a、新生成的對象在Eden區完成內存分配;
b、當Eden區滿了,再創建對象,會因為申請不到空間,觸發minorGC,進行young(eden+1survivor)區的垃圾回收。(為什么是eden+1survivor:兩個survivor中始終有一個survivor是空的,空的那個被標記成To Survivor);
c、minorGC時,Eden不能被回收的對象被放入到空的survivor(也就是放到To Survivor,同時Eden肯定會被清空),另一個survivor(From Survivor)里不能被GC回收的對象也會被放入這個survivor(To Survivor),始終保證一個survivor是空的。(MinorGC完成之后,To Survivor 和 From Survivor的標記互換);
d、當做第3步的時候,如果發現存放對象的那個survivor滿了,則這些對象被copy到old區,或者survivor區沒有滿,但是有些對象已經足夠Old(通過XX:MaxTenuringThreshold參數來設置),也被放入Old區。(對象在Survivor區中每熬過一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15歲)時,就會晉升到老年代中)
(3)標記-整理
old space也可以標記-復制策略嗎?當然不行!
young space中的對象大部分都是生命周期較短的對象,每次GC后,所剩下的活對象數量不是很大。而old space中的對象大部分都是生命周期特別長的對象,即使GC后,仍然會剩下大量的活對象。如果仍然采用復制動作,回收效率會變得非常低。
根據old space的特點,可以采用整理動作。整理時,先清除掉應該清除的對象,然后把存活對象“壓縮”到堆的一端,按順序排放。
動圖:
Old space(+Permanent Space)的垃圾回收是偶爾發生的,被稱為Full GC(主要回收)。Full GC因為需要對整個堆進行回收,包括Young、Old和Perm,所以比Minor GC要慢,因此應該盡可能減少Full GC的次數。在對JVM調優的過程中,很大一部分工作就是對于FullGC的調節。
有如下原因可能導致Full GC:
-
年老代(Tenured)被寫滿
-
持久代(Perm)被寫滿
-
System.gc()被顯示調用
-
上一次GC之后Heap的各域分配策略動態變化
4)、垃圾收集器
垃圾收集算法是內存回收的理論基礎,而垃圾收集器就是內存回收的具體實現。
堆(Heap)分代被目前大部分JVM所采用。它的核心思想是根據對象存活的生命周期將內存劃分為若干個不同的區域。一般情況下將堆區劃分為old space和Young space,old space的特點是每次垃圾收集時只有少量對象需要被回收,而Young space的特點是每次垃圾回收時都有大量的對象需要被回收,那么就可以根據不同代的特點采取最適合的收集算法。
目前大部分垃圾收集器對于Young space都采取“標記-復制”算法。而由于Old space的特點是每次回收都只回收少量對象,一般使用的是“標記-整理”算法。
(1)Young Space上的GC實現:
Serial(串行):?Serial收集器是最基本最古老的收集器,它是一個單線程收集器,并且在它進行垃圾收集時,必須暫停所有用戶線程。Serial收集器是針對新生代的收集器,采用的是“標記-復制”算法。它的優點是實現簡單高效,但是缺點是會給用戶帶來停頓。這個收集器類型僅應用于單核CPU桌面電腦。使用serial收集器會顯著降低應用程序的性能。
ParNew(并行):?ParNew收集器是Serial收集器的多線程版本,使用多個線程進行垃圾收集。
Parallel Scavenge(并行):?Parallel Scavenge收集器是一個新生代的多線程收集器(并行收集器),它在回收期間不需要暫停其他用戶線程,其采用的是“標記-復制”算法,該收集器與前兩個收集器有所不同,它主要是為了達到一個可控的吞吐量。
(2)Old Space上的GC實現:
Serial Old(串行):Serial收集器的Old Space版本,采用的是“標記-整理”算法。這個收集器類型僅應用于單核CPU桌面電腦。使用serial收集器會顯著降低應用程序的性能。
Parallel Old(并行):Parallel Old是Parallel Scavenge收集器的Old Space版本(并行收集器),使用多線程和“標記-整理”算法。
CMS(并發):CMS(Current Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,它是一種并發收集器,采用的是"標記-清除"算法。
(3).G1
G1(Garbage First)收集器是JDK1.7提供的一個新收集器,G1收集器基于“標記-整理”算法實現,也就是說不會產生內存碎片。還有一個特點之前的收集器進行收集的范圍都是整個新生代或老年代,而G1將整個Java堆(包括新生代,老年代)。
3.JVM參數
1).堆
-Xmx:最大堆內存,如:-Xmx512m
-Xms:初始時堆內存,如:-Xms256m
-XX:MaxNewSize:最大年輕區內存
-XX:NewSize:初始時年輕區內存.通常為 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 個 Survivor 空間。實際可用空間為 = Eden + 1 個 Survivor,即 90%
-XX:MaxPermSize:最大持久帶內存
-XX:PermSize:初始時持久帶內存
-XX:+PrintGCDetails。打印 GC 信息
?-XX:NewRatio?新生代與老年代的比例,如 –XX:NewRatio=2,則新生代占整個堆空間的1/3,老年代占2/3
?-XX:SurvivorRatio?新生代中 Eden 與 Survivor 的比值。默認值為 8。即 Eden 占新生代空間的 8/10,另外兩個 Survivor 各占 1/10
2).棧
-xss:設置每個線程的堆棧大小.?JDK1.5+ 每個線程堆棧大小為 1M,一般來說如果棧不是很深的話, 1M 是絕對夠用了的。
3).垃圾回收
4).JVM ?client模式和server模式
Java_home/bin/java命令有一個-server和-client參數,該參數標識了JVM以server模式或client模式啟動。
JVM Server模式與client模式啟動,最主要的差別在于:-Server模式啟動時,速度較慢,但是一旦運行起來后,性能將會有很大的提升。當虛擬機運行在-client模式的時候,使用的是一個代號為C1的輕量級編譯器, 而-server模式啟動的虛擬機采用相對重量級,代號為C2的編譯器. C2比C1編譯器編譯的相對徹底,,服務起來之后,性能更高。
(1)查看當前JVM默認啟動模式
java -version 可以直接查看出默認使用的是client還是 server。
(2)JVM默認啟動模式自動偵測
從JDK 5開始,如果沒有顯式地用-client或者-server參數,那么JVM啟動時,會根據機器配置和JDK的版本,自動判斷該用哪種模式。
-
the definition of a server-class machine is one with at least 2 CPUs and at least 2GB of physical memory.
-
windows平臺,64位版本的JDK,沒有提供-client模式,直接使用server模式。
(3).通過配置文件,改變JVM啟動模式
兩種模式的切換可以通過更改配置(jvm.cfg配置文件)來實現:
32位的JVM配置文件在JAVA_HOME/jre/lib/i386/jvm.cfg,
64位的JVM配置文件在JAVA_HOME/jre/lib/amd64/jvm.cfg, 目前64位只支持server模式。
例如:
32位版本的JDK 5的jvm.cfg文件內容:
?| 1 2 3 4 5 6 | -client?KNOWN -server?KNOWN -hotspot?ALIASED_TO?-client -classic?WARN -native?ERROR -green?ERROR |
64位版本的JDK 7的jvm.cfg文件內容:
?| 1 2 3 4 5 6 | -server?KNOWN -client?IGNORE -hotspot?ALIASED_TO?-server -classic?WARN -native?ERROR -green?ERROR |
?
4.堆 VS 棧
JVM棧是運行時的單位,而JVM堆是存儲的單位。
JVM棧代表了處理邏輯,而JVM堆代表了數據。
JVM堆中存的是對象。JVM棧中存的是基本數據類型和JVM堆中對象的引用。
JVM堆是所有線程共享,JVM棧是線程獨有。
PS:Java中的參數傳遞是傳值呢?還是傳址?
我們都知道:C 語言中函數參數的傳遞有:值傳遞,地址傳遞,引用傳遞這三種形式。但是在Java里,方法的參數傳遞方式只有一種:值傳遞。所謂值傳遞,就是將實際參數值的副本(復制品)傳入方法內,而參數本身不會受到任何影響。
要說明這個問題,先要明確兩點:
1.引用在Java中是一種數據類型,跟基本類型int等等同一地位。
2.程序運行永遠都是在JVM棧中進行的,因而參數傳遞時,只存在傳遞基本類型和對象引用的問題。不會直接傳對象本身。
在運行JVM棧中,基本類型和引用的處理是一樣的,都是傳值。如果是傳引用的方法調用,可以理解為“傳引用值”的傳值調用,即“引用值”被做了一個復制品,然后賦值給參數,引用的處理跟基本類型是完全一樣的。但是當進入被調用方法時,被傳遞的這個引用值,被程序解釋(或者查找)到JVM堆中的對象,這個時候才對應到真正的對象。如果此時進行修改,修改的是引用對應的對象,而不是引用本身,即:修改的是JVM堆中的數據。所以這個修改是可以保持的了。
例如:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package?com.demo3; public?class?DataWrap?{ ????public?int?a; ????public?int?b; } package?com.demo3; public?class?ReferenceTransferTest?{ ????public?static?void?swap(DataWrap?dw)?{ ????????int?tmp?=?dw.a; ????????dw.a?=?dw.b; ????????dw.b?=?tmp; ????} ????public?static?void?main(String[]?args)?{ ????????DataWrap?dw?=?new?DataWrap(); ????????dw.a?=?6; ????????dw.b?=?9; ????????swap(dw); ????} } |
對應的內存圖:
?
附:
?
?
---------------------------------------------------------------------------------------------------------------------------
http://www.cnblogs.com/ggjucheng/p/3977384.html
前言?
JVM GC是JVM的內存回收算法,調整JVM GC(Garbage Collection),可以極大的減少由于GC工作,而導致的程序運行中斷方面的問題,進而適當的提高Java程序的工作效率。但是調整GC是以個極為復雜的過程,所以我們要了解JVM內存組成,回收算法,對象分配機制。
?
JVM 堆內存組成
Java堆由Perm區和Heap區組成,Heap區由Old區和New區(也叫Young區)組成,New區由Eden區、From區和To區(Survivor)組成。
Eden區用于存放新生成的對象。Eden中的對象生命不會超過一次Minor GC。
Survivor Space ?有兩個,存放每次垃圾回收后存活的對象,即圖的S0和S1。
Old Generation ?Old區,也稱老生代,主要存放應用程序中生命周期長的存活對象
?
JVM初始分配的內存由-Xms指定,JVM最大分配的內存由-Xmx指定。默認空余堆內存小于40%時,JVM就會增大堆直到-Xmx的最大限制;空余堆內存大于70%時,JVM會減少堆直到 -Xms的最小限制。因此服務器一般設置-Xms、-Xmx相等以避免在每次GC 后調整堆的大小。
-XX:NewRatio= 參數可以設置Young與Old的大小比例,-server時默認為1:2,如果太小,會使大對象直接分配到old區去,增大major collections的執行的次數,影響性能。
-XX:SurvivorRatio= 參數可以設置Eden與Survivor的比例,默認為1:8,Survivio大了會浪費,如果小了的話,會使一些大對象在做minor gc時,直接從eden區潛逃到old區,讓old區的gc頻繁。這個參數保持默認就好了,一般情況下,對性能影響不大。
?
New區的Collector
?
1、??串行GC(Serial Copying)
? ? ?client模式下的默認GC方式,也可使用-XX:+UseSerialGC指定。
2、??并行回收GC(Parallel Scavenge)
? ? ?server模式下的默認GC方式,也可用-XX:+UseParallelGC強制指定。
? ? ?采用PS時,默認情況下JVM會在運行時動態調整Eden:S0:S1的比例,如果不希望自動調整可以使用-XX:-UseAdaptiveSizePolicy參數,內存分配和回收的算法和串行相同,唯一不同僅在于回收時為多線程。
3、??并行GC(ParNew)
? ? ?CMS GC時默認采用,也可以采用-XX:+UseParNewGC指定。內存分配、回收和PS相同,不同的僅在于會收拾會配合CMS做些處理。
?
Old區的幾種Collector
1、??串行GC(Serial MSC)
? ? ?client模式下的默認GC方式,可通過-XX:+UseSerialGC強制指定。每次進行全部回收,進行Compact,非常耗費時間。
2、??并行GC(Parallel MSC)(備注,吞吐量大,但是gc的時候響應很慢)
? ? server模式下的默認GC方式,也可用-XX:+UseParallelGC=強制指定。可以在選項后加等號來制定并行的線程數。
3、??并發GC(CMS)線上環境采用的GC方式,也就是Realese環境的方式。(備注,響應比并行gc快很多,但是犧牲了一定的吞吐量)
? ? ?使用CMS是為了減少GC執行時的停頓時間,垃圾回收線程和應用線程同時執行,可以使用-XX:+UseConcMarkSweepGC=指定使用,后邊接等號指定并發線程數。CMS每次回收只停頓很短的時間,分別在開始的時候(Initial Marking),和中間(Final Marking)的時候,第二次時間略長。具體CMS的過程可以參考相關文檔。JStat中將Initial Mark和Remark都統計成了FGC。
CMS一個比較大的問題是碎片和浮動垃圾問題(Floating Gabage)。碎片是由于CMS默認不對內存進行Compact所致,可以通過-XX:+UseCMSCompactAtFullCollection。
?
總體來講,Old區的大小較大,垃圾回收算法較費時間,導致較長時間的應用線程停止工作,而且需要進行Compact,所以不應該出現較多Major GC。Major GC的時間常常是Minor GC的幾十倍。JVM內存調優的重點,減少Major GC 的次數,因為為Major GC 會暫停程序比較長的時間,如果Major GC 的次數比較多,意味著應用程序的JVM內存參數需要進行調整。
?
JVM內存分配策略
?
1. 對象優先在Eden分配
如果Eden區不足分配對象,會做一個minor gc,回收內存,嘗試分配對象,如果依然不足分配,才分配到Old區。
2.大對象直接進入老年代
大對象是指需要大量連續內存空間的Java對象,最典型的大對象就是那種很長的字符串及數組,虛擬機提供了一個-XX:PretenureSizeThreshold參數,令大于這個設置值的對象直接在老年代中分配。這樣做的目的是避免在Eden區及兩個Survivor區之間發生大量的內存拷貝(新生代采用復制算法收集內存)。PretenureSizeThreshold參數只對Serial和ParNew兩款收集器有效,
3.長期存活的對象將進入老年代
在經歷了多次的Minor GC后仍然存活:在觸發了Minor GC后,存活對象被存入Survivor區在經歷了多次Minor GC之后,如果仍然存活的話,則該對象被晉升到Old區。
虛擬機既然采用了分代收集的思想來管理內存,那內存回收時就必須能識別哪些對象應當放在新生代,哪些對象應放在老年代中。為了做到這點,虛擬機給每個對象定義了一個對象年齡(Age)計數器。如果對象在Eden出生并經過第一次Minor GC后仍然存活,并且能被Survivor容納的話,將被移動到Survivor空間中,并將對象年齡設為1。對象在Survivor區中每熬過一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15歲)時,就會被晉升到老年代中。對象晉升老年代的年齡閾值,可以通過參數-XX:MaxTenuringThreshold來設置。
4.動態對象年齡判定
為了能更好地適應不同程序的內存狀況,虛擬機并不總是要求對象的年齡必須達到MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。
5.Minor GC后Survivor空間不足就直接放入Old區
6.空間分配擔保
在發生Minor GC時,虛擬機會檢測之前每次晉升到老年代的平均大小是否大于老年代的剩余空間大小,如果大于,則改為直接進行一次Full GC。如果小于,則查看HandlePromotionFailure設置是否允許擔保失敗;如果允許,那只會進行Minor GC;如果不允許,則也要改為進行一次Full GC。大部分情況下都還是會將HandlePromotionFailure開關打開,避免Full GC過于頻繁。
?
JVM GC組合方式
?
?
如何監視GC
1.概覽監視gc。
? ?jmap -heap [pid] 查看內存分布
? ?jstat -gcutil [pid] 1000 每隔1s輸出java進程的gc情況
2.詳細監視gc。
? ?在jvm啟動參數,加入-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log。
? ?輸入示例:
??
[GC [ParNew: 11450951K->1014116K(11673600K), 0.8698830 secs] 27569972K->17943420K(37614976K), 0.8699520 secs] [Times: user=11.28 sys=0.82, real=0.86 secs]? ?表示發生一次minor GC,ParNew是新生代的gc算法,11450951K表示eden區的存活對象的內存總和,1014116K表示回收后的存活對象的內存總和,11673600K是整個eden區的內存總和。0.8699520 secs表示minor gc花費的時間。
? ?27569972K表示整個heap區的存活對象總和,17943420K表示回收后整個heap區的存活對象總和,37614976K表示整個heap區的內存總和。
[Full GC [Tenured: 27569972K->16569972K(27569972K), 180.2368177 secs] 36614976K->27569972K(37614976K), [Perm : 28671K->28635K(28672K)], 0.2371537 secs]
? 表示發生了一次Full GC,整個JVM都停頓了180多秒,輸出說明同上。只是Tenured: 27569972K->16569972K(27569972K)表示的是old區,而上面是eden區。
?
更多可以參考 阿里分享的ppt?sunjdk1.6gc.pptx
轉載于:https://www.cnblogs.com/shengs/p/4945048.html
總結
以上是生活随笔為你收集整理的[转]JVM运行时内存结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaScript设计模式与开发实践
- 下一篇: java连接mongod抛java.ne