-XX:MaxTenuringThreshold
晉升年齡最大閾值,默認15。在新生代中對象存活次數(經過YGC的次數)后仍然存活,就會晉升到老年代。每經過一次YGC,年齡加1,當survivor區的對象年齡達到TenuringThreshold時,表示該對象是長存活對象,就會直接晉升到老年代。
-XX:TargetSurvivorRatio
設定survivor區的目標使用率。默認50,即survivor區對象目標使用率為50%。
JVM會將每個對象的年齡信息、各個年齡段對象的總大小記錄在“age table”表中。基於“age table”、survivor區大小、survivor區目標使用率(-XX:TargetSurvivorRatio)、晉升年齡閾值(-XX:MaxTenuringThreshold),JVM會動態的計算tenuring threshold的值。一旦對象年齡達到了tenuring threshold就會晉升到老年代。
為什么要動態的計算tenuring threshold的值呢?假設有很多年齡還未達到TenuringThreshold的對象依舊停留在survivor區,這樣不利於新對象從eden晉升到survivor。因此設置survivor區的目標使用率,當使用率達到時重新調整TenuringThreshold值,讓對象盡早的去old區。
如果希望跟蹤每次新生代GC后,survivor區中對象的年齡分布,可在啟動參數上增加-XX:+PrintTenuringDistribution。
用法: -XX:MaxTenuringThreshold=3
該參數主要是控制新生代需要經歷多少次GC晉升到老年代中的最大閾值。在JVM中用4個bit存儲(放在對象頭中),所以其最大值是15。
但並非意味着,對象必須要經歷15次YGC才會晉升到老年代中。例如,當survivor區空間不夠時,便會提前進入到老年代中,但這個次數一定不大於設置的最大閾值。
那么JVM到底是如何來計算S區對象晉升到Old區的呢?
首先介紹另一個重要的JVM參數:-XX:TargetSurvivorRatio
:一個計算期望s區存活大小(Desired survivor size)的參數。默認值為50,即50%。
當一個S區中所有的age對象的大小如果大於等於Desired survivor size,則重新計算threshold,以age和MaxTenuringThreshold兩者的最小值為准。
以一個Demo為例:
//-Xmx200M -Xmn50m -XX:TargetSurvivorRatio=60 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:MaxTenuringThreshold=3 //最小堆為50M,默認SurvivorRatio為8,那么可以知道Eden區為40M,S0和S1為5M public class App { public static void main(String[] args) throws InterruptedException { // main方法作為主線程,變量不會被回收 byte[] byte1 = new byte[1 * 1024 * 1024]; byte[] byte2 = new byte[1 * 1024 * 1024]; YGC(40); Thread.sleep(3000); YGC(40); Thread.sleep(3000); YGC(40); Thread.sleep(3000); // 這次再ygc時, 由於byte1和byte2的年齡經過3次ygc后已經達到3(-XX:MaxTenuringThreshold=3), 所以會晉升到old YGC(40); // ygc后, s0(from)/s1(to)的空間為0 Thread.sleep(3000); // 達到TargetSurvivorRatio這個比例指定的值,即 5M(S區)*60%(TargetSurvivorRatio)=3M(Desired survivor size) byte[] byte4 = new byte[1 * 1024 * 1024]; byte[] byte5 = new byte[1 * 1024 * 1024]; byte[] byte6 = new byte[1 * 1024 * 1024]; // 這次ygc時, 由於s區已經占用達到了60%(-XX:TargetSurvivorRatio=60), // 所以會重新計算對象晉升的min(age, MaxTenuringThreshold) = 1 YGC(40); Thread.sleep(3000); // 由於前一次ygc時算出age=1, 所以這一次再ygc時, byte4, byte5, byte6就要晉升到Old, // 而不需要等MaxTenuringThreshold這么多次, 此次ygc后, s0(from)/s1(to)的空間再次為0, 對象全部晉升到old YGC(40); Thread.sleep(3000); System.out.println("GC end!"); } //塞滿Eden區,局部變量會被回收,作為觸發GC的小工具 private static void YGC(int edenSize){ for (int i = 0 ; i < edenSize ; i ++) { byte[] byte1m = new byte[1 * 1024 * 1024]; } } }
可以看到結果
//第一次YGC 2017-07-22T17:43:50.615-0800: [GC (Allocation Failure) 2017-07-22T17:43:50.615-0800: [ParNew: 39936K->2812K(46080K), 0.0126581 secs] 39936K->2812K(125952K), 0.0127387 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] //第二次YGC 2017-07-22T17:43:53.653-0800: [GC (Allocation Failure) 2017-07-22T17:43:53.653-0800: [ParNew: 43542K->2805K(46080K), 0.0144079 secs] 43542K->2805K(125952K), 0.0144607 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] //第三次YGC 2017-07-22T17:43:56.679-0800: [GC (Allocation Failure) 2017-07-22T17:43:56.679-0800: [ParNew: 43329K->2877K(46080K), 0.0010447 secs] 43329K->2877K(125952K), 0.0010784 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] //三次YGC后,此時age達到MaxTenuringThreshold閾值3,再次YGC時,會晉升到Old區,可以看到此時新生代空間為0 2017-07-22T17:43:59.691-0800: [GC (Allocation Failure) 2017-07-22T17:43:59.691-0800: [ParNew: 43604K->0K(46080K), 0.0065182 secs] 43604K->2675K(125952K), 0.0065664 secs] [Times: user=0.01 sys=0.01, real=0.00 secs] //分配3M不回收的對象,經歷一次YGC,此時age=1 2017-07-22T17:44:02.708-0800: [GC (Allocation Failure) 2017-07-22T17:44:02.708-0800: [ParNew: 40731K->3072K(46080K),