Java記憶體模型
總的來說就分為兩個區域,堆記憶體(Heap)和非堆記憶體(No-Heap),非堆記憶體又稱為永久代(Permanent),永久的意思其實是針對于垃圾回收器來說的,表示這部分内容不需要回收。在新的JDK8中,這部分的名稱已經不叫Permanent了,改成更好了解的Metaspace了。這部分是用來存儲JVM工作的相關資料的,比如Load下來的class定義、靜态變量、用于排程的方法和線程棧。
為什麼要分為這兩個區域呢?其實也是為了垃圾回收器用的,堆記憶體用來存儲Java對象執行個體,是以這部分才需要進行回收。而非堆記憶體是JVM自己的工作空間,屬于JVM實作的一部分,JVM在實作的時候已經自己實作了記憶體回收。
值得注意的是,在GC日志中,你會發現PSPermGen也在其中:
[GC-- [PSYoungGen: 569856K->569856K(617472K)] 773089K->928306K(976896K), 0.3285123 secs] [Times: user=0.66 sys=0.08, real=0.33secs]
[Full GC [PSYoungGen: 569856K->0K(617472K)] [ParOldGen: 358450K->359349K(499712K)] 928306K->359349K(1117184K) [PSPermGen: 64652K->64650K(131072K)], 1.4693823 secs] [Times: user=4.09 sys=0.02, real=1.47secs]
[Full GC [PSYoungGen: 569856K->0K(617472K)] [ParOldGen: 359349K->488638K(672768K)] 929205K->488638K(1290240K) [PSPermGen: 65041K->65041K(131072K)], 2.4092196 secs] [Times: user=6.75 sys=0.11, real=2.41 secs]
意思是No-Heap(永久代)也需要垃圾回收?
确實是這樣的,因為在永久代中還存儲着ClassLoader、Class的元資訊(Metadata)、指向Heap區域對象的指針以及字元串池(Internal String)。這些資料其實也需要垃圾回收。
這樣看來,把記憶體區域劃分為年輕代(Young Gen)、老年代(Old Gen)和永久代(Permanent Gen)其實是有道理的,雖然老年代的資料不會提升到永久代中。但是這三個區域的資料都是需要垃圾回收的。
Heap(堆記憶體)
堆記憶體是垃圾回收器工作的地方,堆記憶體又分為Eden和兩個大小一樣的Survivor區,即From和To。
最開始的對象都會存儲在Eden中(如果有些大對象無法存入到年輕代,則會直接存入老年代),然後經過Minor GC之後會被提升到Survivor區,然後再提升到老年代。
Stack(棧記憶體)
Thread Stack 用來存放棧資訊,每個線程棧資訊裡各自有自己的方法棧(包括本地方法棧),在方法棧的每一幀裡存儲着方法調用的相關資訊,比如參數值、局部變量、傳回值等。
Program Counter:記錄着目前語句執行到哪兒了。
下圖中,其實Stack可以歸并到No-Heap中。
垃圾回關注的名額
吞吐量
定義:使用者代碼執行時間 / ( 使用者代碼執行時間 + 垃圾回收時間)
越高越好,越高表示執行垃圾回收時間越少。
暫停時間
回收時可能需要暫停使用者線程,暫停時間越短越好。
執行頻率
機關時間垃圾回收執行的次數。
堆記憶體大小
比如G1回收器就要去比較大的Heap記憶體。
敏感度(Promptness)
對象變成垃圾到被回收的時間,時間越短表示回收器越敏感。
垃圾回收類型
串行搜集器(Serial)
年輕代(Young Gen)回收
老年代(Old Gen)回收
來年代的回收很簡單,步驟是“标記-清除-壓縮”:
串行回收器的使用場景
一般引用于不要求“低暫停”的client模式。這裡參考server和client的差別。j2se5的client模式下預設使用串行回收器進行垃圾回收。
使用串行回收器的參數:-XX:+UseSerialGC
并行回收器(Paraller)
并行回收器可以利用多個CPU進行并行的垃圾回收。(在多個CPU場景下,使用Serial回收器時,其實隻有一個CPU在工作,其他CPU相當于閑置狀态)
并行回收器在年輕地啊和老年代進行垃圾回收的操作是一樣的,都是标記、轉移、壓縮,隻是它啟用了多個CPU并發執行。串行和并行都需要stop-the-world。
并行回收器的使用場景
應用于多CPU場景下,但是沒有太大的暫停時間要求,因為還是有可能會發生長時間的老年代垃圾回收。
适用于批處理、賬單、财務、科學計算等場景。
j2se5的server模式下預設使用該回收器。
使用并行回收器的參數:-XX:+UseParallelGC
ParNew
這是一個加強版的Parallel回收器。它可以與下面提到的CMS回收器進行配合。
并行壓縮回收器(Parallel Compacting)
年輕代使用并行回收器一樣的算法(多CPU并行回收)。(stop-the-world)
-XX:+UseParallelOldGC.
對于老年代,回收過程分為三個階段
并行标記(Marking)
标記出每個區域的活動資料。标記動作其實是和使用者線程一起跑的。
計算總結(Summary)
計算各個區域的稠密程度,得出移動資料的方案。(稀疏的往稠密位置移動,壓縮速度肯定比相反方向要快)
壓縮(Compaction)
把資料移動到一端,保證另外一端空白。
并行壓縮回收器的使用場景
多CPU,它相對于并行回收器感覺沒有什麼差別(誰知道嗎???),因為在官方文檔中是這樣說的:
使用該回收器的參數:-XX:-UseParallelOldGC
并發标記清理回收器(Concurrent Mark-Sweep (CMS))
低延遲的回收器。
一般而言,年輕代的回收不會有太大的暫停。而老年代的回收不會經常執行,是以老年代的回收暫停時間長一點兒也沒事。CMS的回收步驟如下:
年輕代還是使用并發回收器(Parallel)。
對于老年代:
初始标記(init mark):單線程stop-the-world執行,短暫停,确定出直接和程式關聯的活動對象集合。
并發标記(concurrent marking):根據上一步标記出來的集合,并發找出與集合中元素關聯的其他活動對象。這一步的執行是和使用者線程并發執行的。
重新标記(remark):由于使用者線程也在運作,是以上一步标記出來的對象還有可能又參數了垃圾,是以這裡再次stop-the-world,重新找出活動資料。這一步對現場并發執行。很容易了解,這裡的stop-the-world也是非常短的。
清理(sweep):根據上一步标記結果,清理記憶體。
從CMS的執行步驟可以看出,它的核心思想其實就是把事情分成多個步驟來做,不要一次把事情做完,且盡量和使用者線程一起執行,進而實作對使用者線程的低延遲。
CMS回收器使用場景
任何需要低延遲的應用。甚至在單CPU上都運作良好。
使用CMS回收器參數:-XX:+UseConcMarkSweepGC
G1(Gargage First)回收器
G1回收器是用于server模式下,多處理器、大記憶體的環境。其目标是高吞吐量、高可用性。在JDK7 update4以後的版本中都支援。長期計劃中G1是用來代替CMS回收器的。
G1不像其他回收器那樣,把記憶體明确地劃分為三個固定大小的區域。而是把heap看成一個整體。
更多細節詳見參考連接配接。
垃圾收集器參數總結
收集器設定:
-XX:+UseSerialGC:年輕串行(Serial),老年串行(Serial Old)
-XX:+UseParNewGC:年輕并行(ParNew),老年串行(Serial Old)
-XX:+UseConcMarkSweepGC:年輕并行(ParNew),老年串行(CMS),備份(Serial Old)
-XX:+UseParallelGC:年輕并行吞吐(Parallel Scavenge),老年串行(Serial Old)
-XX:+UseParalledlOldGC:年輕并行吞吐(Parallel Scavenge),老年并行吞吐(Parallel Old)
收集器參數:
-XX:ParallelGCThreads=n:設定并行收集器收集時使用的CPU數。并行收集線程數。
-XX:MaxGCPauseMillis=n:設定并行收集最大暫停時間
-XX:GCTimeRatio=n:設定垃圾回收時間占程式運作時間的百分比。公式為1/(1+n)
-XX:+CMSIncrementalMode:設定為增量模式。适用于單CPU情況。
-XX:ParallelGCThreads=n:設定并發收集器年輕代收集方式為并行收集時,使用的CPU數。并行收集線程數。
參考: