JVM内存结构分析:为什么需要S0和S1?
一、為什么會有年輕代
我們先來屢屢,為什么需要把堆分代?不分代不能完成他所做的事情么?其實不分代完全可以,分代的唯一理由就是優(yōu)化GC性能。你先想想,如果沒有分代,那我們所有的對象都在一塊,GC的時候我們要找到哪些對象沒用,這樣就會對堆的所有區(qū)域進行掃描。而我們的很多對象都是朝生夕死的,如果分代的話,我們把新創(chuàng)建的對象放到某一地方,當GC的時候先把這塊存“朝生夕死”對象的區(qū)域進行回收,這樣就會騰出很大的空間出來。
二.年輕代中的GC
新生代大小(PSYoungGen total 9216K)=eden大小(eden space 8192K)+1個survivor大小(from space 1024K)
HotSpot JVM把年輕代分為了三部分:1個Eden區(qū)和2個Survivor區(qū)(分別叫from和to)。默認比例為8(Eden):1(一個survivor),為啥默認會是這個比例,接下來我們會聊到。一般情況下,新創(chuàng)建的對象都會被分配到Eden區(qū)(一些大對象特殊處理),這些對象經過第一次Minor GC后,如果仍然存活,將會被移到Survivor區(qū)。對象在Survivor區(qū)中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度時,就會被移動到年老代中。
因為年輕代中的對象基本都是朝生夕死的(80%以上),所以在年輕代的垃圾回收算法使用的是復制算法,復制算法的基本思想就是將內存分為兩塊,每次只用其中一塊,當這一塊內存用完,就將還活著的對象復制到另外一塊上面。復制算法不會產生內存碎片。
在GC開始的時候,對象只會存在于Eden區(qū)和名為“From”的Survivor區(qū),Survivor區(qū)“To”是空的。緊接著進行GC,Eden區(qū)中所有存活的對象都會被復制到“To”,而在“From”區(qū)中,仍存活的對象會根據他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被復制到“To”區(qū)域。經過這次GC后,Eden區(qū)和From區(qū)已經被清空。這個時候,“From”和“To”會交換他們的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名為To的Survivor區(qū)域是空的。Minor GC會一直重復這樣的過程,直到“To”區(qū)被填滿,“To”區(qū)被填滿之后,會將所有對象移動到年老代中。
三、一個對象的這一輩子
我是一個普通的Java對象,我出生在Eden區(qū),在Eden區(qū)我還看到和我長的很像的小兄弟,我們在Eden區(qū)中玩了挺長時間。有一天Eden區(qū)中的人實在是太多了,我就被迫去了Survivor區(qū)的“From”區(qū),自從去了Survivor區(qū),我就開始漂了,有時候在Survivor的“From”區(qū),有時候在Survivor的“To”區(qū),居無定所。直到我18歲的時候,爸爸說我成人了,該去社會上闖闖了。于是我就去了年老代那邊,年老代里,人很多,并且年齡都挺大的,我在這里也認識了很多人。在年老代里,我生活了20年(每次GC加一歲),然后被回收。
四、為什么要有Survivor區(qū)
先不去想為什么有兩個Survivor區(qū),第一個問題是,設置Survivor區(qū)的意義在哪里?
如果沒有Survivor,Eden區(qū)每進行一次Minor GC,存活的對象就會被送到老年代。老年代很快被填滿,觸發(fā)Major GC(因為Major GC一般伴隨著Minor GC,也可以看做觸發(fā)了Full GC)。老年代的內存空間遠大于新生代,進行一次Full GC消耗的時間比Minor GC長得多。你也許會問,執(zhí)行時間長有什么壞處?頻發(fā)的Full GC消耗的時間是非常可觀的,這一點會影響大型程序的執(zhí)行和響應速度,更不要說某些連接會因為超時發(fā)生連接錯誤了。
好,那我們來想想在沒有Survivor的情況下,有沒有什么解決辦法,可以避免上述情況:
顯而易見,沒有Survivor的話,上述兩種解決方案都不能從根本上解決問題。
我們可以得到第一條結論:Survivor的存在意義,就是減少被送到老年代的對象,進而減少Full GC的發(fā)生,Survivor的預篩選保證,只有經歷16次Minor GC還能在新生代中存活的對象,才會被送到老年代。
五、為什么要設置兩個Survivor區(qū)
設置兩個Survivor區(qū)最大的好處就是解決了碎片化,下面我們來分析一下。
為什么一個Survivor區(qū)不行?第一部分中,我們知道了必須設置Survivor區(qū)。假設現在只有一個survivor區(qū),我們來模擬一下流程:
剛剛新建的對象在Eden中,一旦Eden滿了,觸發(fā)一次Minor GC,Eden中的存活對象就會被移動到Survivor區(qū)。這樣繼續(xù)循環(huán)下去,下一次Eden滿了的時候,問題來了,此時進行Minor GC,Eden和Survivor各有一些存活對象,如果此時把Eden區(qū)的存活對象硬放到Survivor區(qū),很明顯這兩部分對象所占有的內存是不連續(xù)的,也就導致了內存碎片化。
我繪制了一幅圖來表明這個過程。其中色塊代表對象,白色框分別代表Eden區(qū)(大)和Survivor區(qū)(小)。Eden區(qū)理所當然大一些,否則新建對象很快就導致Eden區(qū)滿,進而觸發(fā)Minor GC,有悖于初衷。
碎片化帶來的風險是極大的,嚴重影響Java程序的性能。堆空間被散布的對象占據不連續(xù)的內存,最直接的結果就是,堆中沒有足夠大的連續(xù)內存空間,接下去如果程序需要給一個內存需求很大的對象分配內存。。。畫面太美不敢看。。。這就好比我們爬山的時候,背包里所有東西緊挨著放,最后就可能省出一塊完整的空間放相機。如果每件行李之間隔一點空隙亂放,很可能最后就要一路把相機掛在脖子上了。
那么,順理成章的,應該建立兩塊Survivor區(qū),剛剛新建的對象在Eden中,經歷一次Minor GC,Eden中的存活對象就會被移動到第一塊survivor space S0,Eden被清空;等Eden區(qū)再滿了,就再觸發(fā)一次Minor GC,Eden和S0中的存活對象又會被復制送入第二塊survivor space S1(這個過程非常重要,因為這種復制算法保證了S1中來自S0和Eden兩部分的存活對象占用連續(xù)的內存空間,避免了碎片化的發(fā)生)。S0和Eden被清空,然后下一輪S0與S1交換角色,如此循環(huán)往復。如果對象的復制次數達到16次,該對象就會被送到老年代中。下圖中每部分的意義和上一張圖一樣,就不加注釋了。
上述機制最大的好處就是,整個過程中,永遠有一個survivor space是空的,另一個非空的survivor space無碎片。
那么,Survivor為什么不分更多塊呢?比方說分成三個、四個、五個?顯然,如果Survivor區(qū)再細分下去,每一塊的空間就會比較小,很容易導致Survivor區(qū)滿,因此,我認為兩塊Survivor區(qū)是經過權衡之后的最佳方案。
原文:https://www.cnblogs.com/duanxz/p/6076662.html
總結
以上是生活随笔為你收集整理的JVM内存结构分析:为什么需要S0和S1?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 防范ddos攻击的方式有哪些(防范DDO
- 下一篇: 一文理清Http2.0