JVM內存分配策略


-------------------------------------------------------------------------------JVM內存分配策略-----------------------------------------------------------------------------------------

一:對象內存分配兩種方法

為對象分配空間的任務等同於把一塊確定大小的內存從Java堆中划分出來。

  • 指針碰撞(Serial、ParNew等帶Compact過程的收集器) 假設Java堆中內存是絕對規整的,所有用過的內存都放在一邊,空閑的內存放在另一邊,中間放着一個指針作為分界點的指示器,那所分配內存就僅僅是把那個指針向空閑空間那邊挪動一段與對象大小相等的距離,這種分配方式稱為“指針碰撞”(Bump the Pointer)。
  • 空閑列表(CMS這種基於Mark-Sweep算法的收集器) 如果Java堆中的內存並不是規整的,已使用的內存和空閑的內存相互交錯,那就沒有辦法簡單地進行指針碰撞了,虛擬機就必須維護一個列表,記錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間划分給對象實例,並更新列表上的記錄,這種分配方式稱為“空閑列表”(Free List)。

選擇哪種分配方式由Java堆是否規整決定,而Java堆是否規整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。因此,在使用Serial、ParNew等帶Compact過程的收集器時,系統采用的分配算法是指針碰撞,而使用CMS這種基於Mark-Sweep算法的收集器時,通常采用空閑列表。

 

二:Java對象分配流程

三:棧上分配

     我們通過JVM內存分配可以知道JAVA中的對象都是在堆上進行分配,當對象沒有被引用的時候,需要依靠GC進行回收內存,如果對象數量較多的時候,會給GC帶來較大壓力,也間接影響了應用的性能。為了減少臨時對象在堆內分配的數量,JVM通過逃逸分析確定該對象不會被外部訪問。那就通過標量替換將該對象分解在棧上分配內存,這樣該對象所占用的內存空間就可以隨棧幀出棧而銷毀,就減輕了垃圾回收的壓力。

       逃逸分析:逃逸分析是編譯語言中的一種優化分析,而不是一種優化的手段。通過對象的作用范圍的分析,為其他優化手段提供分析數據從而進行優化。包括全局變量賦值逃逸,方法返回值逃逸,實例引用發生逃逸,線程逃逸:賦值給類變量或可以在其他線程中訪問的實例變量。

       標量替換:標量即不可被進一步分解的量,而JAVA的基本數據類型就是標量(如:int,long等基本數據類型以及reference類型等),標量的對立就是可以被進一步分解的量,而這種量稱之為聚合量。而在JAVA中對象就是可以被進一步分解的聚合量。通過逃逸分析確定該對象不會被外部訪問,並且對象可以被進一步分解時,JVM不會創建該對象,而會將該對象成員變量分解若干個被這個方法使用的成員變量所代替。這些代替的成員變量在棧幀或寄存器上分配空間。

   

四:TLAB分配

五:內存分配規則

     (使用Serial/Serial Old收集器)

        一:對象優先分配在Eden區:大多數情況下,對象在新生代中的Eden區分配,當Eden區沒有足夠空間進行分配時,虛擬機將發生一次Minor GC。

        二:大對象直接進入老年代:大對象是指,需要大量連續內存空間的Java對象,虛擬機提供了相關參數調整大小。

        三:長期存活的對象進入老年代:每次Minor GC,年齡就增加一歲,默認15歲,進入老年代,也可以通過參數調整。

        四:動態對象年齡判定:如果在Survivor空間中相同年齡所有對象大小的總和大小大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代。

        五:空間分配擔保:在發生Minor GC前,虛擬機會檢查老年代的最大可用連續空間是否大於新生代所有對象的總空間,成立安全,不成立,觸發Full GC。

 

六:測試Demo

AllocDemo.java),工具IDEA,如下圖配置VM參數

public class AllocDemo {
    class User {
        int id;
        String name;
        User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }
    private void alloc(int i) {
        User user = new User(i, "name" + i);
    }
    public static void main(String[] args) {
        AllocDemo a = new AllocDemo();
        long s1 = System.currentTimeMillis();
        for (int i = 0; i < 10000000; i++) {
            a.alloc(i);
        }
        long s2 = System.currentTimeMillis();
        System.out.println("time:"+(s2 - s1));//記錄分配時間
    }
}

 

 

  關閉逃逸分析,關閉標量替換,關閉TLAB分配        設置參數: -XX:-DoEscapeAnalysis  -XX:-EliminateAllocations -XX:-UseTLAB  -XX:+PrintGCDetails

 

 關閉逃逸分析,關閉標量替換,開啟TLAB分配      設置參數:-XX:-DoEscapeAnalysis  -XX:-EliminateAllocations -XX:+UseTLAB  -XX:+PrintGCDetails

開啟逃逸分析,開啟標量替換,開啟TLAB分配       設置:-XX:+DoEscapeAnalysis  -XX:+EliminateAllocations -XX:+UseTLAB  -XX:+PrintGCDetails

    如果想看java堆上的對象分布情況

    1.通過jps查看Class的Main進程的PID 

    2. jmap -histo [pid]查看java堆上的對象分布情況 。||  jmap -dump:format=b,file=文件名.hprof pid生成dump文件

    如果不熟悉jps,jmap等用法的可以參考官網:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/

 

   內容主要來源:《深入理解Java虛擬機》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM