1. 程式人生 > >JVM深入瞭解,一些冷門容易被忽略的知識(Eden、Survivor、JDK8記憶體改動等)

JVM深入瞭解,一些冷門容易被忽略的知識(Eden、Survivor、JDK8記憶體改動等)

提醒讀者,以下所講的內容不權威,僅值得參考,若有錯誤或者需要討論的地方,請留言,我會認真查看回復。

本部落格僅僅適合JVM初識者,適合作為一個小小的進階。

如果你對JVM還沒有任何瞭解的話,建議閱讀:JVM記憶體分配初探

  • Stack:
    此區域會發生OOM,建立的執行緒佔用的不是JVM的記憶體,而是OS分配給java程序的記憶體,所以,當請求分配的執行緒數量大於這個程序允許的最大執行緒數時,會發生OOM。
    最大執行緒數的一個計算公式:
    (MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
        MaxProcessMemory OS分配給java程序的最大記憶體
        JVMMemory         JVM記憶體
        ReservedOsMemory  保留的作業系統記憶體
        ThreadStackSize      執行緒棧的大小

    關於Stack的VM引數:
        -Xss512m 執行緒所佔有的Stack棧大小

    呼叫的一個函式佔一個棧幀,如果申請的棧幀數量大於Stack的深度,那麼就會發生StackOverFlow異常:

    public void test(){
            test();
        }
    一旦呼叫這個test()函式就會發生StackOverFlow異常

  • Heap:
        還可以被分為新生代(Eden+Survivor1+Survivor2)、老生代(Tenured)、常量池(下面會介紹)。
    劃分Heap區為以上新生代、老生代的一個理由就是為了GC的回收策略:分代回收。
        它的核心思想是根據物件存活的生命週期將記憶體劃分為若干個不同的區域。一般情況下將堆區劃分為老年代(Tenured Generation)和新生代(Young Generation),老年代的特點是每次垃圾收集時只有少量物件需要被回收,而新生代的特點是每次垃圾回收時都有大量的物件需要被回收,那麼就可以根據不同代的特點採取最適合的收集演算法。
    對於常量池:
        在JDK1.6及之前,常量池是被劃分在Perm Generation(持久代)中,但是JDK1.7及以後,就把常量池歸到了Heap區,這段程式碼可以證明:(注:String.intern()方法是將Heap區的String物件實體複製到常量池,說白了就是往常量池中新增物件)

            int i = 0;
            List list = new ArrayList();
            while(true){
                list.add(String.valueOf(i++).intern());
            }

    JDK1.6:java.lang.OutOfMemoryError: PermGen space

    JDK1.7:java.lang.OutOfMemoryError: Java heap space

     所以,你可以說在JDK1.7及之後,Heap區分為:新生代(Eden+Survivor1+Survivor2)、老生代(Tenured)、常量池。

        新生代大小VM引數:-Xmn20m 。

        注此引數應當比-Xms -Xmx(heap區的預設值和最大值)要小,否則:
        設定VM引數:-Xms10m -Xmx20m -Xmn30m,執行java程式碼,得到警告:
        Java HotSpot(TM) 64-Bit Server VM warning: MaxNewSize (30720k) is equal to or greater than the entire heap (20480k).  A new max generation size of 19968k will be used. 

  • Permanent Generation()持久代
    有時候也稱為方法區,在該區內很少發生垃圾回收,在這裡進行的GC主要是方法區裡的常量池和類的解除安裝
    方法區主要用來儲存已被虛擬機器載入的類資訊、常量、靜態變數和即時編譯後的程式碼等資料
    方法區裡有一個執行時常量池,用於存放靜態編譯產生的字面量和符號引用。執行時生成的常量也會存在這個常量池中(當然,在JDK1.7及之後已經被劃分到Heap區了)
    -XX:MaxPermSize設定上限
    -XX:PermSize設定最小值
        例:VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M

    但是,注意:JDK8中已經完全移除了永久帶!
        所以說以上的引數-XX:MaxPermSize、-XX:PermSize已經完全沒有用了。    
        在移除了Perm區域之後,JDK 8中使用MetaSpace來替代,對應的VM引數變成:
           -XX:MetaspaceSize=512m 初始值
           -XX:MaxMetaspaceSize=550m 最大值
    至於為什麼有這個改動那就不是很清楚,還望有了解的人賜教。
  • Minor GC 和 Full GC
    Minor GC發生在Eden,Survivor1區,比較頻繁,在Eden區朝生夕死的物件很多,當物件填滿Eden區時候觸發Minor GC。本次GC任然存活的物件被移到Survivor2。
    Full GC發生在老生代,老生代比例為新生代的1/8(一般),佔用小,執行Full GC後任然存活的Survivor 2中的物件被移到老生代。
    詳細推薦閱讀

如有錯誤或者疑問,歡迎留言。

再次註明,本文所涉及內容不權威,僅值得參考。