天天看點

JVM堆記憶體和非堆記憶體

1 堆記憶體和非堆記憶體

JVM記憶體劃分為堆記憶體和非堆記憶體,堆記憶體分為年輕代(Young Generation)、老年代(Old Generation),非堆記憶體就一個永久代(Permanent Generation)。

年輕代又分為Eden和Survivor區。Survivor區由FromSpace和ToSpace組成。Eden區占大容量,Survivor兩個區占小容量,預設比例是8:1:1。

官方推薦新生代占堆的的3/8,Survivor占新生代的1/10。

堆記憶體用途:存放的是對象,垃圾收集器就是收集這些對象,然後根據GC算法回收。

非堆記憶體用途:永久代,也稱為方法區,存儲程式運作時長期存活的對象,比如類的中繼資料、方法、常量、屬性等。

在JDK1.8版本廢棄了永久代,替代的是元空間(MetaSpace),元空間與永久代上類似,都是方法區的實作,他們最大差別是:永久代使用的是JVM的堆記憶體空間,而元空間使用的是實體記憶體,直接受到本機的實體記憶體限制。在後面的實踐中,因為筆者使用的是JDK8,是以列印出的GC日志裡面就有MetaSpace。

2 JVM堆内部構型(新生代和老年代)

Jdk8中已經去掉永久區,這裡為了與時俱進,不再贅餘。

JVM堆記憶體和非堆記憶體

上圖示範Java堆記憶體空間,分為新生代和老年代,分别占Java堆1/3和2/3的空間,新生代中又分為Eden區、Survivor0區、Survivor1區,分别占新生代8/10、1/10、1/10空間。

問題1:什麼是Java堆?

回答1:JVM規範中說到:”所有的對象執行個體以及數組都要在堆上配置設定”。Java堆是垃圾回收器管理的主要區域,百分之九十九的垃圾回收發生在Java堆,另外百分之一發生在方法區,是以又稱之為”GC堆”。根據JVM規範規定的内容,Java堆可以處于實體上不連續的記憶體空間中,但是邏輯上要求是連續的。

問題2:為什麼Java堆要分為新生代和老年代?

回答2:目前JVM對于堆的垃圾回收,采用分代收集的政策。根據堆中對象的存活周期将堆記憶體分為新生代和老年代。在新生代中,每次垃圾回收都有大批對象死去,隻有少量存活。而老年代中存放的對象存活率高。這樣劃分的目的是為了使 JVM 能夠更好的管理堆記憶體中的對象,包括記憶體的配置設定以及回收。

問題3:為什麼新生代要分為Eden區、Survivor0區、Survivor1區?

回答3:這是結構與政策相适應的原則,新生代垃圾收集使用的是複制算法(一種垃圾收集算法,Serial收集器、ParNew收集器、Parallel scavenge收集器都是用這種算法),複制算法可以很好的解決垃圾收集的記憶體碎片問題,但是有一個天然的缺陷,就是要犧牲一半的記憶體(即任意時刻隻有一半記憶體用于工作),這對于寶貴的記憶體資源來說是極度奢侈的。新生代在使用複制算法作為其垃圾收集算法的時候,對其做了優化,拿出2/10的新生代的記憶體作為交換區,稱為Survivor0區和Survivor1區(注意:有的部落格上稱為From Survivor Space和To Survivor Space,這樣闡述也是對的,但是容易對初學者形成誤導,因為在複制算法中,複制是雙向的,沒有固定的From和To,這一次是由這一邊到另一邊,下次就是從另一邊到這一邊,使用From Survivor Space和To Survivor Space容易讓後來學習者誤以為複制隻能從一邊到另一邊,當然有的部落格中會附加不管從哪邊到哪邊,起始就是From,終點就是To,即From Survivor Space和To Survivor Space所對應的區循環對調,但是讀者不一定想的明白。是以筆者這裡使用Survivor0、Survivor1,減少誤解)

是以說,新生代在結構上分為Eden區、Survivor0區、Survivor1區,是與其使用的垃圾收集算法(複制算法)相适應的結果。

問題4:關于永久區Permanent Space?

回答4:由于Jdk8中取消了永久區Permanent Space,本文為與時俱進,不再講述Permanent Space。