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