在Java虛擬機運行時數據區中,堆內存是各種內存中最大的一塊。堆內存的建立伴隨着虛擬機的啓動而建立。全部對象實例的建立都是在堆內存中。在Java虛擬機規範中明確的描述了:全部對象實例以及數組都要在堆上分配內存空間。垃圾回收的主要區域也是發生在堆內存中。算法
從內存回收的角度來看,如今的垃圾收集器基本上都採用了分代收集算法,因此,堆內存又能夠分爲新生代和老年代。數組
在爲新建立的對象分配內存時,爲了保證併發的安全,在堆空間中會爲每一個線程建立一個TLAB(本地線程分配緩衝區)區域,因此雖然堆內存是線程共享的,可是在內存分配的角度來看,它又可以劃分出多個線程私有的TLAB區域。安全
固然,重點關注的仍是堆內存中的新生代與老年代,首先看一下堆空間中新生代與老年代的劃分比例:併發
新生代與老年代的空間大小比例爲1:2,而在新生代中還劃分了三個區域:Eden區、Surivivor From區、Surivivor To區。它們之間的比例分別爲8:1:1。下面來詳細分析不一樣年齡代的區域。性能
新生代主要做用是用來放置新建立的對象,任何一個對象初次建立時都會被分配至此區域。在新生代中,98%的對象都是"朝生夕死"的,並不須要一比一來劃份內存空間,而是講內存空間劃分爲兩個大的區域:Eden區、Survivor區(其中,Surivivor區域又被劃分紅兩個Survivor From區,Survivor To區域)。每一次內存分配都是使用Eden區和Survivor區域中的一塊。既然劃分了三個區域,那麼就來講說對象在這三個區域中怎麼流轉的。線程
強調的一點是,新生代中,只會Eden區和Survivor區域其中的一塊被同時使用,另外一塊Survivor區域始終爲空的。cdn
上面說到,Survivor區域中,始終會有一塊Survivor區域被空置,那麼在有限的堆內存中,豈不是形成了內存的浪費。首先了解一下劃分出Survivor區域的意義在哪裏。對象
試想若是沒有劃分Survivor區域,那麼Eden區每進行一次Monir GC,都會將對象直接送入老年代,老年代將會很快被填滿,從而觸發Major GC。Major GC的執行效率相對Monir GC來講效率慢了十倍以上。那麼與有Survivor區的狀況相比,Major GC觸發的頻率則會相對提升,嚴重的將會影響到程序執行及相應的速度。blog
若是存在Survivor區域,它能夠做爲一個緩衝區,當Eden區觸發Monir GC後,對象不直接送往老年代,而是複製到Survivor區,相對來講就能夠下降觸發Major GC的機率。因此,Survivor區存在的根本意義是:減小對象被送往老年代的頻率,從而減小Major GC和Full GC的發生,Survivor能夠保證在經歷了16次Monir GC還能在新生代存活的對纔會被送到老年代。內存
另一個方面就是Survivor區有效的解決了內存的碎片化。回顧以前說到的新生代對象流轉流程。新建立的對象都被分配在Eden區,一旦該區域的內存滿了,會觸發Monir GC,而後對象會被從Eden送至Survivor區,往復循環。由此,問題來了,在進行Monir GC時,Eden和Survivor區都有一切存活的對象,此時將Eden取中的對象強行存放到Survivor區時,明顯兩部分的對象所佔用的內存空間是不連續的,也就致使了內存的碎片化。
內存碎片化最終的結果就是會嚴重影響到程序的性能。試想當堆空間被散佈的對象佔據了不連續的內存,當有一個內存需求較大的對象被建立的時,堆中可能就沒有足夠大的連續內存空間來分配給該對象,那麼就會觸發Full GC。
若是將Survivor區劃分紅兩塊。在Eden區剛剛建立新對象時,經歷一次Monir GC,Eden區存活的對象就被被複制移動到第一塊Survivor區中,Eden被清空,等Eden區再滿了的時,再次觸發Monir GC,Eden區和第一塊Survivor區存活的對象會被複制移動到第二塊Survivor區。Eden和第一塊Survivor區域被清空。
因此,爲何要劃分兩個Survivor區呢?
老年代主要存放的是經歷過幾回垃圾回收以後還存活的對象,剛剛在說到新生代對象的複製轉移的時候,當被標記了16次的對象若是還存活着,就會被送入到老年代。
另一種就是較大的對象,較大的對象也就被直接送入到老年代中。
不怕路歹行不怕大雨淋,心上一字敢 面對個人夢,甘願來做憨人。 --<憨人>