JVM 之(7)內存分配

堆內存劃分爲 新生代(Eden空間、Survivor空間)和 老年代(Tenured/Old 空間)。
安全



1.對象優先在Eden分配

大可能是狀況下,對象在新生代Eden區中分配。當Eden區中沒有足夠空間進行分配時,虛擬機將發起一次Minor GC



-verbose:gc  -XX:+PrintGCDetails  -XX:+PrintGCTimeStamps 打印gc日誌
 -Xms20M -Xmx20M 指定堆內存
-Xmn10M 指定新生代內存
-XX:SurvivorRatio=8 指定Eden區與一個Survivor區的空間比例是8:1。

併發

private static final int _1MB = 1024 * 1024;

public static void main(String[] args) {
    byte[] allocation1 = new byte[2 * _1MB];
    byte[] allocation2 = new byte[2 * _1MB];
    byte[] allocation3 = new byte[2 * _1MB];
    byte[] allocation4 = new byte[4 * _1MB];
}
[GC (Allocation Failure) [PSYoungGen: 6420K->1010K(9216K)] 6420K->3594K(19456K), 0.0157046 secs] [Times: user=0.00 sys=0.00, real=0.02 secs] 
Heap
 PSYoungGen      total 9216K, used 5427K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 53% used [0x00000000ff600000,0x00000000ffa50630,0x00000000ffe00000)
  from space 1024K, 98% used [0x00000000ffe00000,0x00000000ffefc888,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 6680K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 65% used [0x00000000fec00000,0x00000000ff286298,0x00000000ff600000)
 Metaspace       used 3305K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 357K, capacity 388K, committed 512K, reserved 1048576K
    PSYoungGen     使用了Parallel收集器,關於JVM垃圾收集器漫談,能夠參考《JVM 之(5)垃圾收集器》

分配allocation4對象的語句會發生一次MinorGC,給allocation4分配內存時,發現Eden已經被佔用了6MB,剩餘空間已不足分配all4所需的4MB內存,所以發生一次MinorGC。高併發

    GC期間,虛擬機又發現已有的3個2MB大小的對象所有沒法放入Survivor空間(Survivor空間只有1MB大小),因此只好經過分配擔保機制提早分配到老年代去。性能

    GC結束後,4MBallocation4對象順利分配在Eden中,Survivor空閒,老年代被佔用6MB。


this

2.大對象直接進入老年代

    對於體積較大的對象,直接進入老年代區域而不是分配到新生代。
    JVM參數-XX:PretenureSizeThreshold的意思就是將體積大於這個設置值的對象直接在老年代分配。 這樣作是爲了不在Eden區及兩個Survivor區之間發生大量的內存複製。
    PretenureSizeThreshold參數只對 Serial 和 ParNew兩款收集器有效



spa

private static final int _1MB = 1024 * 1024;

public static void main(String[] args) {

    byte[] allocation = new byte[7 * _1MB];
}
 PSYoungGen      total 9216K, used 4537K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 55% used [0x00000000ff600000,0x00000000ffa6e478,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 7168K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 70% used [0x00000000fec00000,0x00000000ff300010,0x00000000ff600000)
 Metaspace       used 3326K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 361K, capacity 388K, committed 512K, reserved 1048576K

3.長期存活的對象將進入老年代

    既然虛擬機採用了分代收集的思想來管理內存,那麼內存回收時就必須能識別哪些對象應放在新生代,哪些對象應放在老年代中。 
爲了作到這點,虛擬機給每一個對象定義了一個對象年齡(Age)計數器。 若是對象在Eden出生並通過第一次Minor GC後仍然存活,而且能被Survivor容納的話,將被移動到Survivor空間中,而且對象年齡設爲1。 
    對象在Survivor區中每「熬過」一次Minor GC,年齡就增長1歲,當它的年齡增長到必定程度(默認爲15歲),就將會被晉升到老年代中。
對象晉升老年代的年齡閾值,能夠經過參數-XX:MaxTenuringThreshold設置。

.net

4.對象年齡的動態斷定

    爲了能更好地適應不一樣程序的內存情況,虛擬機並非永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代。 
    若是在Survivor空間中相同年齡全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。


線程

5. 空間分配擔保
    在發生Minor GC以前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代全部對象總空間,若是這個條件成立,那麼Minor GC能夠確保是安全的。 
    若是不成立,則虛擬機會查看 - XX:HandlePromotionFailure設置值是否容許擔保失敗。 
    若是容許,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小,若是大於,將嘗試着進行一次Minor GC,儘管此次Minor GC是有風險的; 

    若是小於,或者-XX:HandlePromotionFailure設置不容許冒險,那這時也要改成進行一次Full GC。日誌

    觸發Full GC執行的狀況:
        1. System.gc();
        2 . 老年代空間不足
        3. Permanet Generation空間滿
        4. CMS GC時出現promotion failed和concurrent mode failure(《JVM 之(5)垃圾收集器》中提到)
        5.  統計獲得的Minor GC晉升到老年代的平均大小大於舊生代的剩餘空間
對象



6. 棧上分配與逃逸分析(Escape Analysis)

1.  什麼是棧上分配?
   棧上分配主要是指在Java程序的執行過程當中,在方法體中聲明的變量以及建立的對象,將直接從該線程所使用的棧中分配空間。 通常而言,建立對象都是從堆中來分配的,這裏是指在棧上來分配空間給新建立的對象。


2.  什麼是逃逸?
   逃逸是指在某個方法以內建立的對象,除了在方法體以內被引用以外,還在方法體以外被其它變量引用到;這樣帶來的後果是在該方法執行完畢以後,該方法中建立的對象將沒法被GC回收,因爲其被其它變量引用。正常的方法調用中,方法體中建立的對象將在執行完畢以後,將回收其中建立的對象;故因爲沒法回收,即成爲逃逸。


public class StackAllocation {

    public StackAllocation obj;

    /**  * 方法返回StackAllocation對象,發生逃逸  * @return  */  public StackAllocation getInstance(){
        return  obj == null ? new StackAllocation() : obj;
    }

    /**  * 爲成員變量賦值,發生逃逸  */  public void setObj(){
        this.obj = new StackAllocation();
    }

    /**  * 對象做用域在方法體內,沒有發生逃逸  */  public void useStackAllocation(){
        StackAllocation stackAllocation = new StackAllocation();
    }

    /**  * 引用成員變量,發生逃逸  */  public void useStackAllocationObj(){
        StackAllocation stackAllocation = getInstance();
    }
}

 3.  逃逸分析   
    在JDK 6以後支持對象的棧上分析和逃逸分析,在JDK 7中徹底支持棧上分配對象。 其是否打開逃逸分析依賴於如下JVM的設置: -XX:+DoEscapeAnalysis  

4. 棧上分配與逃逸分析的關係  
    進行逃逸分析以後,產生的後果是全部的對象都將由棧上分配,而非從JVM內存模型中的堆來分配 。

5. 逃逸分析/棧上分配的優劣分析

     優點表如今如下兩個方面:

  •    消除同步。線程同步的代價是至關高的,同步的後果是下降併發性和性能。逃逸分析能夠判斷出某個對象是否始終只被一個線程訪問,若是隻被一個線程訪問,那麼對該對象的同步操做就能夠轉化成沒有同步保護的操做,這樣就能大大提升併發程度和性能。
  •  矢量替代。逃逸分析方法若是發現對象的內存存儲結構不須要連續進行的話,就能夠將對象的部分甚至所有都保存在CPU寄存器內,這樣能大大提升訪問速度。
      劣勢:  棧上分配受限於棧的空間大小,通常自我迭代類的需求以及大的對象空間需求操做,將致使棧的內存溢出;故只適用於必定範圍以內的內存範圍請求。
相關文章
相關標籤/搜索