Sun JDK 1.6 GC
                               (Garbage Collector)
                                       http://bluedavy.com
                                          2010-05-13 V0.2
                                          2010-05-19 V0.5
                                          2010-06-01 V0.8




ppt中未特别强调的JVM均指Sun JDK 1.6.0
Java:自动内存管理
     为什么还需要学习GC?


   OOM?
   GC成为支撑更高并发量的瓶颈?
only介绍

使用
通常问题查找
Tuning
实现
GC:Garbage Collector

   不是只负责内存回收

   还决定了内存分配
使用
Hotspot是如何分配内存的

Hotspot什么时候回收内存
内存结构
          -Xss

         局部变量区       本地方法栈
PC寄      操作数栈                       -XX:PermSize –

存器         栈帧
                     JVM方法区         XX:MaxPermSize



        JVM方法栈         JVM堆        -Xms -Xmx




备注:在Hotspot中本地方法栈和JVM方法栈是同一个,因此也可用-Xss控制
内存分配
1、堆上分配
  大多数情况在eden上分配,偶尔会直接在old上分配
  细节取决于GC的实现
  这里最重要的优化是TLAB

2、栈上分配
  原子类型的局部变量
  或基于EA后标量替换转变为原子类型的局部变量

3、堆外分配
  DirectByteBuffer
  或直接使用Unsafe.allocateMemory,但不推荐这种方式
内存回收(Garbage Collection)
GC要做的是将那些dead的对象所占用的内存回收掉;

1、Hotspot认为没有引用的对象是dead的

2、Hotspot将引用分为四种
 Strong、Soft、Weak、Phantom
 Strong即默认通过Object o=new Object()这种方式赋值的引用;
 Soft、Weak、Phantom这三种则都是继承Reference;
 在Full GC时会对Reference类型的引用进行特殊处理:
   Soft:内存不够时一定会被GC、长期不用也会被GC,可通过
         -XX:SoftRefLRUPolicyMSPerMB来设置;
   Weak:一定会被GC,当被mark为dead,会在ReferenceQueue
          中通知;
   Phantom:本来就没引用,当从jvm heap中释放,会通知。
内存回收


经IBM研究,通常运行的程序有98%的对象是临时对象,因此
Sun Hotspot对JVM堆采用了分代的方式来管理,以提升GC的
效率。
JVM堆:分代

-Xmn   New Generation

  Eden             S0        S1   Old Generation
         -XX:SurvivorRatio




备注:通常将对新生代进行的回收称为Minor GC;对旧生代进行的回收称为Major GC,但由于
Major GC除并发GC外均需对整个堆进行扫描和回收,因此又称为Full GC。
新生代可用GC
 串行GC                   并行回收GC
              并行GC
 (Serial                 (Parallel
             (ParNew)
Copying)                Scavenge)




           我该用哪个呢?
新生代可用GC—串行
1. client模式下默认GC方式,也可通过-XX:+UseSerialGC来强制指定;

2. eden、s0、s1的大小通过-XX:SurvivorRatio来控制,默认为8,含义
   为eden:s0的比例,启动后可通过jmap –heap [pid]查看。
新生代可用GC—串行
默认情况下,仅在TLAB或eden上分配,只有两种状况会在旧生代分配:
1、需要分配的大小超过eden space大小;
2、在配置了PretenureSizeThreshold的情况下,对象大小大于此值。
 public class SerialGCDemo{
   public static void main(String[] args) throws Exception{
      byte[] bytes=new byte[1024*1024*2];
      byte[] bytes2=new byte[1024*1024*2];
      byte[] bytes3=new byte[1024*1024*2];
      Thread.sleep(3000);
      byte[] bytes4=new byte[1024*1024*4];
      Thread.sleep(3000);
   }
}

             -Xms20M –Xmx20M –Xmn10M –XX:+UseSerialGC

 -Xms20M –Xmx20M –Xmn10M -XX:PretenureSizeThreshold=3145728 –XX:+UseSerialGC
新生代可用GC—串行
当eden space空间不足时触发。

 public class SerialGCDemo{
   public static void main(String[] args) throws Exception{
      byte[] bytes=new byte[1024*1024*2];
      byte[] bytes2=new byte[1024*1024*2];
      byte[] bytes3=new byte[1024*1024*2];
      System.out.println(“step 1");
      byte[] bytes4=new byte[1024*1024*2];
      Thread.sleep(3000);
      System.out.println(“step 2");
      byte[] bytes5=new byte[1024*1024*2];
      byte[] bytes6=new byte[1024*1024*2];
      System.out.println(“step 3");
      byte[] bytes7=new byte[1024*1024*2];
      Thread.sleep(3000);
   }
}


                 -Xms20M –Xmx20M –Xmn10M –XX:+UseSerialGC
新生代可用GC—串行
上面示例之所以会是触发一次minor和一次full,在于Serial GC的这个规则:

在回收前Serial GC会先检测之前每次Minor GC时晋升到旧生代的平均大小是否大
于旧生代的剩余空间,如大于,则直接触发full,如小于,则取决于
HandlePromotionFailure的设置。
新生代可用GC—串行
 public class SerialGCDemo{
   public static void main(String[] args) throws Exception{
      byte[] bytes=new byte[1024*1024*2];
      byte[] bytes2=new byte[1024*1024*2];
      byte[] bytes3=new byte[1024*1024*2];
      System.out.println("step 1");
      bytes=null;
      byte[] bytes4=new byte[1024*1024*2];
      Thread.sleep(3000);
      System.out.println("step 2");
      byte[] bytes5=new byte[1024*1024*2];
      byte[] bytes6=new byte[1024*1024*2];
      bytes4=null;
      bytes5=null;
      bytes6=null;
      System.out.println("step 3");
      byte[] bytes7=new byte[1024*1024*2];
      Thread.sleep(3000);
   }
}


                  -Xms20M –Xmx20M –Xmn10M –XX:+UseSerialGC

       -Xms20M –Xmx20M –Xmn10M -XX:-HandlePromotionFailure –XX:+UseSerialGC
新生代可用GC—串行
上面示例在两个参数时执行效果之所以不同,在于Serial GC的这个规则:

触发Minor GC时:
之前Minor GC晋级到old的平均大小 < 旧生代剩余空间 < eden+from使用空间
当HandlePromotionFailure为true,则仅触发minor gc,如为false,则触发
  full。
新生代可用GC—串行
新生代对象晋升到旧生代的规则

1、经历多次minor gc仍存活的对象,可通过以下参数来控制:
  以MaxTenuringThreshold值为准,默认为15。

2、to space放不下的,直接放入旧生代;
新生代可用GC—串行
public class SerialGCThreshold{                                  -Xms20M –Xmx20M –
  public static void main(String[] args) throws Exception{            Xmn10M –
     SerialGCMemoryObject object1=new SerialGCMemoryObject(1);
     SerialGCMemoryObject object2=new SerialGCMemoryObject(8);
                                                                   XX:+UseSerialGC
     SerialGCMemoryObject object3=new SerialGCMemoryObject(8);
     SerialGCMemoryObject object4=new SerialGCMemoryObject(8);
     object2=null;
     object3=null;
                                                                 -Xms20M –Xmx20M –
     SerialGCMemoryObject object5=new SerialGCMemoryObject(8);        Xmn10M –
     Thread.sleep(4000);                                           XX:+UseSerialGC
     object2=new SerialGCMemoryObject(8);
     object3=new SerialGCMemoryObject(8);                                 -
     object2=null;                                               XX:MaxTenuringThres
     object3=null;
     object5=null;
                                                                       hold=1
     SerialGCMemoryObject object6=new SerialGCMemoryObject(8);
     Thread.sleep(5000);
  }
}
class SerialGCMemoryObject{
  private byte[] bytes=null;
  public SerialGCMemoryObject(int multi){
    bytes=new byte[1024*256*multi];
  }
}
新生代可用GC—串行
把上面代码中的object1修改为如下:
 SerialGCMemoryObject object1=new SerialGCMemoryObject(2);


           -Xms20M –Xmx20M –Xmn10M –XX:+UseSerialGC
新生代可用GC—串行
上面示例中object1在第二次minor gc时直接转入了old,在于Serial GC的
这个规则:

每次Minor GC后会重新计算TenuringThreshold
(第一次以MaxTenuringThreshold为准)

计算的规则为:
累积每个age中的字节,当这个累计值 > To Space的一半时,对比此时的age和
MaxTenuringThreshold,取其中更小的值。

可通过PrintTenuringDistribution来查看下次minor gc时的TenuringThreshold
值:Desired survivor size 524288 bytes, new threshold 1 (max 15),其中
的new threshold 1即为新的TenuringThreshold的值。

例如在上面的例子中:
当第一次Minor GC结束时,遍历age table,当累积age 1的字节后,发现此时所
占用的字节数 > To Space的一半,因此将TenuringThreshold赋值为1,下次
Minor GC时即把age超过1的对象全部转入old。
JVM新生代可用GC—串行
[GC [DefNew: 11509K->1138K(14336K), 0.0110060 secs] 11509K-
  >1138K(38912K),
  0.0112610 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]
新生代可用GC—ParNew
1. CMS GC时默认采用,也可采用-XX:+UseParNewGC强制指定;

2. eden、s0、s1的大小通过-XX:SurvivorRatio来控制,默认为8,含义
   为eden:s0的比例。

默认情况下其内存分配和回收和Serial完全相同,只是回收的时候为多线程
而已,但一旦开启-XX:+UseAdaptiveSizePolicy则有些不同。
新生代可用GC—ParNew
[GC [ParNew: 11509K->1152K(14336K), 0.0129150 secs] 11509K-
  >1152K(38912K),
0.0131890 secs] [Times: user=0.05 sys=0.02, real=0.02 secs]

如启动参数上设置了-XX:+UseAdaptiveSizePolicy,则会输出
[GC [ASParNew: 7495K->120K(9216K), 0.0403410 secs] 7495K-
  >7294K(19456K), 0.0406480 secs] [Times: user=0.06
  sys=0.15, real=0.04 secs]
新生代可用GC—PS
1. server模式时默认的GC方式,也可采用-XX:+UseParallelGC强制指定;

2. eden、s0、s1的大小可通过-XX:SurvivorRatio来控制,但默认情况下
   以-XX:InitialSurivivorRatio为准,此值默认为8,代表的为
   新生代大小 : s0,这点要特别注意。
新生代可用GC—PS
大多数情况下,会在TLAB或eden上分配。

如下一段代码:
 public class PSGCDemo{
   public static void main(String[] args) throws Exception{
      byte[] bytes=new byte[1024*1024*2];
      byte[] bytes2=new byte[1024*1024*2];
      byte[] bytes3=new byte[1024*1024*2];
      Thread.sleep(3000);
      byte[] bytes4=new byte[1024*1024*4];
      Thread.sleep(3000);
   }
}

 -Xms20M –Xmx20M –Xmn10M –XX:SurvivorRatio=8 –XX:+UseParallelGC
新生代可用GC—PS
上面示例中的bytes4之所以会直接在旧生代分配,在于PS GC的这个规则:


当TLAB、eden上分配都失败时,判断需要分配的内存大小是否 >= eden
space的一半大小,如是就直接在旧生代分配。
新生代可用GC—PS
eden space分配不下,且需要分配的对象大小未超过eden space的一半或old区分配失败,
触发回收;

 public class PSGCDemo{
   public static void main(String[] args) throws Exception{
      byte[] bytes=new byte[1024*1024*2];
      byte[] bytes2=new byte[1024*1024*2];
      byte[] bytes3=new byte[1024*1024*2];
      System.out.println(“step 1");
      byte[] bytes4=new byte[1024*1024*2];
      Thread.sleep(3000);
      System.out.println(“step 2");
      byte[] bytes5=new byte[1024*1024*2];
      byte[] bytes6=new byte[1024*1024*2];
      System.out.println(“step 3");
      byte[] bytes7=new byte[1024*1024*2];
      Thread.sleep(3000);
   }
}


  -Xms20M –Xmx20M –Xmn10M –XX:SurvivorRatio=8 –XX:+UseParallelGC
                -XX:+PrintGCDetails –XX:verbose:gc
新生代可用GC—PS
上面示例之所以会是触发一次minor和两次full,在于PS GC的这个规则:

1、在回收前PS GC会先检测之前每次PS GC时晋升到旧生代的平均大小是否大于
  旧生代的剩余空间,如大于,则直接触发full;

2、在回收后,也会按上面规则进行检测。
新生代可用GC—PS
新生代对象晋升到旧生代的规则

1、经历多次minor gc仍存活的对象,可通过以下参数来控制:
  AlwaysTenure,默认false,表示只要minor GC时存活,就晋升到旧生代;
  NeverTenure,默认false,表示永不晋升到旧生代;
  上面两个都没设置的情况下,如UseAdaptiveSizePolicy,启动时以
  InitialTenuringThreshold值作为存活次数的阈值,在每次ps gc后会动态调整
  如不使用UseAdaptiveSizePolicy,则以MaxTenuringThreshold为准。

2、to space放不下的,直接放入旧生代;
新生代可用GC—PS
在回收后,如UseAdaptiveSizePolicy,PS GC会根据运行状况动态调整eden、to
以及TenuringThreshold的大小。
不希望动态调整可设置-XX:-UseAdaptiveSizePolicy。
如希望跟踪每次的变化情况,可在启动参数上增加: PrintAdaptiveSizePolicy
新生代可用GC—PS
[GC [PSYoungGen: 11509K->1184K(14336K)] 11509K-
   >1184K(38912K), 0.0113360 secs]
  [Times: user=0.03 sys=0.01, real=0.01 secs]
旧生代可用的GC
  串行GC          并行 MS GC         并行 Compacting GC       并发GC
(Serial MSC)   (Parallel MSC)   (Parallel Compacting)   (CMS)




                    我该用哪个呢?
旧生代可用GC—串行
client方式下默认GC方式,可通过-XX:+UseSerialGC强制指定。
旧生代可用GC—串行
触发机制
1、old gen空间不足;
2、perm gen空间不足;
3、minor gc时的悲观策略;
4、minor GC后在eden上分配内存仍然失败;
5、执行heap dump时;
6、外部调用System.gc,可通过-XX:+DisableExplicitGC来禁止。


ps: 如CollectGen0First为true(默认为false),则先执行minor GC;
旧生代可用GC—串行
[Full GC [Tenured: 9216K->4210K(10240K), 0.0066570 secs]
   16584K->4210K(19456K), [Perm : 1692K-
   >1692K(16384K)], 0.0067070 secs]
    [Times: user=0.00 sys=0.00, real=0.01 secs]
旧生代可用GC—并行MSC
1. server模式下默认GC方式,可通过-XX:+UseParallelGC强制指定;

2. 并行的线程数
   cpu core<=8 ? cpu core : 3+(cpu core*5)/8
   或通过-XX:ParallelGCThreads=x来强制指定。
旧生代可用GC—并行MSC
触发机制和串行完全相同

ps: 如ScavengeBeforeFullGC为true(默认值),则先执行minor GC;
旧生代可用GC—并行MSC
[Full GC [PSYoungGen: 1208K->0K(8960K)] [PSOldGen: 6144K-
   >7282K(10240K)] 7352K->7282K(19200K) [PSPermGen: 1686K-
   >1686K(16384K)], 0.0165880 secs] [Times: user=0.01
   sys=0.01, real=0.02 secs]
旧生代可用GC—并行Compacting
1. 可通过-XX:+UseParallelOldGC强制指定;

2. 并行的线程数
   cpu core<=8 ? cpu core : 3+(cpu core*5)/8
   或通过-XX:ParallelGCThreads=x来强制指定。
旧生代可用GC—并行Compacting
触发机制和并行MSC完全相同
旧生代可用GC—并行Compacting

[Full GC [PSYoungGen: 1224K->0K(8960K)] [ParOldGen: 6144K-
   >7282K(10240K)] 7368K->7282K(19200K) [PSPermGen: 1686K-
   >1685K(16384K)], 0.0223510 secs] [Times: user=0.02
   sys=0.06, real=0.03 secs]
旧生代可用GC—并发
可通过-XX:+UseConcMarkSweepGC来强制指定,并发的线程数
默认为:( 并行GC线程数+3)/4,也可通过ParallelCMSThreads指定;
旧生代可用GC—并发
触发机制

1、当旧生代空间使用到一定比率时触发;
  JDK V 1.6中默认为92%,可通过PrintCMSInitiationStatistics(此参数在V
  1.5中不能用)来查看这个值到底是多少;
  可通过CMSInitiatingOccupancyFraction来强制指定,默认值并不是赋值在
  了这个值上,是根据如下公式计算出来的:
  ((100 - MinHeapFreeRatio) +(double)(CMSTriggerRatio * MinHeapFreeRatio) / 100.0)/ 100.0;
  MinHeapFreeRatio默认值: 40 CMSTriggerRatio默认值: 80


2、当perm gen采用CMS收集且空间使用到一定比率时触发;
  perm gen采用CMS收集需设置:-XX:+CMSClassUnloadingEnabled
  JDK V 1.6中默认为92%;
  可通过CMSInitiatingPermOccupancyFraction来强制指定,同样,它是根据
  如下公式计算出来的:
  ((100 - MinHeapFreeRatio) +(double)(CMSTriggerPermRatio* MinHeapFreeRatio) / 100.0)/ 100.0;
  MinHeapFreeRatio默认值: 40 CMSTriggerPermRatio默认值: 80
旧生代可用GC—并发
触发机制

3、Hotspot根据成本计算决定是否需要执行CMS GC;
  可通过-XX:+UseCMSInitiatingOccupancyOnly来去掉这个动态执行的策
  略。

4、外部调用了System.gc,且设置了ExplicitGCInvokesConcurrent;
  需要注意,在JDK 6中,在这种情况下如应用同时使用了NIO,可能会出现
  bug。
旧生代可用GC—并发
public class CMSGCOccur{

    public static void main(String[] args) throws Exception{
      byte[] bytes=new byte[1024*1024*2];
       byte[] bytes1=new byte[1024*1024*2];
      byte[] bytes2=new byte[1024*1024*2];
       byte[] bytes3=new byte[1024*1024*1];
       byte[] bytes4=new byte[1024*1024*2];
       Thread.sleep(5000);
    }

}



          -Xms20M –Xmx20M –Xmn10M -XX:+UseConcMarkSweepGC -
           XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintGCDetails

          -Xms20M –Xmx20M –Xmn10M -XX:+UseConcMarkSweepGC -
                          XX:+PrintGCDetails
旧生代可用GC—并发
1. Promotion Failed
   minor GC了,to space空间不够,往old跑,old也满了,so..
   解决方法:增大to space,增大old,或降低cms gc触发时机

2. Concurrent mode failure
   old要分配内存了,但old空间不够,此时cms gc正在进行,so..
   解决方法:增大old,降低cms gc触发的old所占比率。


在这两种情况下,为了安全,JVM转为触发Full GC。
旧生代可用GC—并发
[GC [1 CMS-initial-mark: 13433K(20480K)] 14465K(29696K), 0.0001830 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-mark: 0.004/0.004 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
CMS: abort preclean due to time [CMS-concurrent-abortable-preclean: 0.007/5.042 secs]
[Times: user=0.00 sys=0.00, real=5.04 secs]
[GC[YG occupancy: 3300 K (9216 K)][Rescan (parallel) , 0.0002740 secs]
[weak refs processing, 0.0000090 secs]
[1 CMS-remark: 13433K(20480K)] 16734K(29696K), 0.0003710 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

当在启动参数上设置了-XX:+UseAdaptiveSizePolicy后,上面的日志中的CMS会变为ASCMS



CMS GC Log解读
GC—默认组合

          新生代GC方式   旧生代和持久代GC方式

Client    串行GC      串行GC

Server    并行回收GC    并行 MSC GC(JDK 5.0 Update 6以后)
GC—组合使用
                          新生代GC方式                   旧生代和持久代GC方式

-XX:+UseSerialGC          串行GC                      串行GC
-XX:+UseParallelGC        PS GC                     并行MSC GC
-XX:+UseConcMarkSweepGC   ParNew GC                 并发GC
                                                    当出现concurrent Mode
                                                    failure时采用串行GC

-XX:+UseParNewGC          并行GC                      串行GC
-XX:+UseParallelOldGC     PS GC                     并行Compacting GC



-XX:+UseConcMarkSweepGC   串行GC                      并发GC
-XX:-UseParNewGC                                    当出现Concurrent Mode
                                                    failure或promotion failed
                                                    时则采用串行GC




不支持的组合方式                  1、-XX:+UseParNewGC –XX:+UseParallelOldGC
                          2、-XX:+UseParNewGC –XX:+UseSerialGC
GC方式                        常用参数(-Xms –Xmx –Xmn –XX:PermSize –XX:MaxPermSize)

新生代可用GC       串行GC          -XX:SurvivorRatio,默认为8,代表eden:survivor;
                            -XX:MaxTenuringThreshold,默认为15,代表对象在新生代经历多少次minor gc后才晋升到
                            旧生代;
              PS GC         -XX:InitialSurvivorRatio,默认为8,代表new gen:survivor;
                            -XX:SurvivorRatio,默认值对于PS GC无效,但仍然可设置,代表eden:survivor;
                            -XX:-UseAdaptiveSizePolicy , 不 允 许 PS GC 动 态 调 整 eden 、 s0 、 s1 的 大 小 , 此 时 -
                            XX:MaxTenuringThreshold也可使用;
                            -XX:ParallelGCThreads,设置并行GC的线程数。

              ParNew GC     同串行。

旧生代和持久代可用GC   串行GC          无特殊参数。
              并行GC          -XX:ParallelGCThreads,设置并行GC的线程数。
              (包括MSC、       -XX:+ScavengeBeforeFullGC,Full GC前触发Minor GC
              Compacting)
              并发GC          -XX:ParallelCMSThreads,设置并发CMS GC时的线程数;
                            -XX:CMSInitiatingOccupancyFraction,当旧生代使用比率占到多少百分比时触发CMS GC;
                            -XX:+UseCMSInitiatingOccupancyOnly,默认为false,代表允许hotspot根据成本来决定什么
                            时候执行CMS GC;
                            -XX:+UseCMSCompactAtFullCollection,当Full GC时执行压缩;
                            -XX:CMSMaxAbortablePrecleanTime=5000,设置preclean步骤的超时时间,单位为毫秒;
                            -XX:+CMSClassUnloadingEnabled,Perm Gen采用CMS GC回收。
JVM GC—可见的未来


    Garbage First

       超出范围,以后再扯
通常问题查找
OOM怎么办?

GC怎么监测?

谁耗了内存?
OOM(一些代码造成OOM的例子)
Java Heap OOM产生的原因是在多次gc后仍然分配不了,具体策略取决于这三个
参数:
-XX:+UseGCOverheadLimit   -XX:GCTimeLimit=98 –XX:GCHeapFreeLimit=2

1、OOM的前兆通常体现在每次Full GC后旧生代的消耗呈不断上涨趋势;
  查看方法:jstat –gcutil [pid] [intervel] [count]

2、解决方法
  dump多次Full GC后的内存消耗状况,方法:
  jmap –dump:format=b,file=[filename] [pid]
  dump下来的文件可用MAT进行分析,简单视图分析:MAT Top Consumers

   或在启动参数上增加:-XX:+HeapDumpOnOutOfMemoryError,当OOM
   时会在工作路径(或通过-XX:HeapDumpPath来指定路径)下生成
   java_[pid].hprof文件。

还有Native Heap造成的OOM,堆外内存使用过多。
GC监测
1、jstat –gcutil [pid] [intervel] [count]

2、-verbose:gc // 可以辅助输出一些详细的GC信息
  -XX:+PrintGCDetails // 输出GC详细信息
  -XX:+PrintGCApplicationStoppedTime // 输出GC造成应用暂停的时间
  -XX:+PrintGCDateStamps // GC发生的时间信息
  -XX:+PrintHeapAtGC // 在GC前后输出堆中各个区域的大小
  -Xloggc:[file] // 将GC信息输出到单独的文件中

   gc的日志拿下来后可使用GCLogViewer或gchisto进行分析。

3、图形化的情况下可直接用jvisualvm进行分析。
谁耗了内存
1、对于长期消耗的,好办,直接dump,MAT就一目了然了;

2、对于短期消耗的,比较麻烦,非图形界面暂时还没有什么好办法,图形界面情况
  下可使用jvisualvm的memory profiler或jprofiler。
Tuning
如何做
case 1 – 场景
 4 cpu,os: linux 2.6.18 32 bit
 启动参数
    ◦ -Xms1536M –Xmx1536M –Xmn500M
 系统响应速度大概为100ms;
 当系统QPS增长到40时,机器每隔5秒就执行一
  次minor gc,每隔3分钟就执行一次full gc,并
  且很快就一直full了;
 每次Full gc后旧生代大概会消耗400M,有点多
  了。
case 1 – 目标和方法
   减少Full GC次数,以避免由于GC造成频繁的长
    暂停,从而导致难以支撑高并发量。

   方法
    ◦   降低响应时间或请求次数,这个需要重构,比较麻烦;
    ◦   减少旧生代内存的消耗,比较靠谱;
    ◦   减少每次请求的内存的消耗,貌似比较靠谱;
    ◦   降低GC造成的应用暂停的时间。
case 1 – tuning
   减少旧生代内存的消耗
    ◦ jmap dump;
    ◦ 发现除了一些确实需要在旧生代中消耗的内存外,还有
      点诡异现象;



     可以肯定的是这里面的线程大部分是没有在处理任务的;

     于是根据MAT查找到底谁消耗掉了这些内存;
      ◦ 发现是由于有一个地方使用到了ThreadLocal,但在使用完毕后
      没有去将ThreadLocal.set(null)。
case 1 – tuning
   在做完上面的tuning后,旧生代的内存使用下降
    了大概200M,效果是full gc的频率稍微拖长了
    一点,但仍然不理想,于是旧生代部分无法继续
    优化了;

   想减少每次请求所分配的内存,碰到的巨大问
    题:
    ◦ 怎么才知道呢?貌似没办法
    ◦ 想了一些方法,放弃了。
case 1 – tuning
   降低GC所造成的长暂停
    ◦ 采用CMS GC
    ◦ QPS只能提升到50…

    ◦ 于是放弃,而且还有和jmap –heap的冲突。
case 1 – tuning
   终极必杀技
    ◦ 降低系统响应时间
     QPS终于能支撑到90…
case 2 – 场景
   4 cpu,os: linux 2.6.18 32 bit
   启动参数
    ◦ -server -Xms1536m -Xmx1536m –Xmn700m
   在系统运行到67919.837秒时发生了一次Full GC,日志信息
    如下:
    67919.817: [GC [PSYoungGen: 588706K->70592K(616832K)]
     1408209K->906379K(1472896K),
                 0.0197090 secs] [Times: user=0.06 sys=0.00,
     real=0.02 secs]
    67919.837: [Full GC [PSYoungGen: 70592K->0K(616832K)]
                 [PSOldGen: 835787K->375316K(856064K)]
     906379K->375316K(1472896K)
                 [PSPermGen: 64826K->64826K(98304K)],
     0.5478600 secs]
                 [Times: user=0.55 sys=0.00, real=0.55 secs]
case 2 – 场景
   之后minor gc的信息如下
case 2 – 场景
   在68132.893时又发生了一次Full GC,日志信息如下:
    ◦ 68132.862: [GC [PSYoungGen: 594736K-
      >63715K(609920K)] 1401225K->891090K(1465984K),
                  0.0309810 secs] [Times: user=0.06
      sys=0.01, real=0.04 secs]
    ◦ 68132.893: [Full GC [PSYoungGen: 63715K-
      >0K(609920K)]
                 [PSOldGen: 827375K->368026K(856064K)]
      891090K->368026K(1465984K)
                 [PSPermGen: 64869K-
      >64690K(98304K)], 0.5341070 secs]
                 [Times: user=0.53 sys=0.00, real=0.53
      secs]

   之后的时间的GC基本也在重复上述过程。
case 2 – 目标和方法
   目标
    ◦ 降低full gc的频率,以及gc所造成的应用暂停;
    ◦ 如果在达到上面目标的情况下,还能降低minor gc的
      频率以及所造成的应用暂停时间就好了。

   方法
    ◦ 降低响应时间或请求次数,比较麻烦;
    ◦ 减少每次请求所需分配的内存,貌似比较麻烦;
    ◦ 减少每次minor gc晋升到old的对象,比较靠谱。
case 2 – tuning
   减少每次minor gc晋升到old的对象
    ◦ 调大new,在当前的参数下不好操作;
    ◦ 调大Survivor,根据目前的状况,是可选的方法;
    ◦ 调大TenuringThreshold,根据目前的状况,这不是
      关键点。
case 2 – tuning
   调大Survivor
    ◦ 当前为PS GC方式,Survivor space会被动态调整,有些
      时候会调整的很小,所以导致了经常有对象直接跳到了
      old;
    ◦ 于是不让动态调整了,-XX:-UseAdaptiveSizePolicy
    ◦ 计算Survivor Space需要的大小,简单的计算了下
      看目前的to space的大小,然后minor gc后晋升到old的,
       old+to space的大小作为需要的大小;
      统计多次后做平均;
    ◦ 于是调整…
    ◦ 继续观察,并做微调,保证高峰期以及一定的冗余。
    ◦ 做过这个调整后,效果很明显,minor gc更频繁了些,但
      full的频率推迟到了至少两小时一次。
case 2 – tuning
   上面的tuning达到了降低full gc的目标,但整体GC
    所造成的响应时间下降的仍然不够多,大概只下降了
    10%;

   于是保持Survivor space,同时将GC方式切换为
    CMS GC。
    ◦ -Xms1536m -Xmx1536m -Xmn700m -
      XX:SurvivorRatio=7 -XX:+UseConcMarkSweepGC -
      XX:+UseCMSCompactAtFullCollection -
      XX:CMSMaxAbortablePrecleanTime=1000 -
      XX:+CMSClassUnloadingEnabled -
      XX:+UseCMSInitiatingOccupancyOnly -
      XX:+DisableExplicitGC
GC Tuning
           1、评估现状



  5、细微调整            2、设定目标




    4、衡量调优    3、尝试调优
GC Tuning—衡量现状
1. 衡量工具
   -XX:+PrintGCDetails –XX:+PrintGCApplicationStoppedTime
   -Xloggc: {文件名} –XX:+PrintGCTimeStamps
   jmap(由于每个版本jvm的默认值可能会有改变,建议还是用jmap
   首先观察下目前每个代的内存大小、GC方式)
   jstat、jvisualvm、sar 、gclogviewer 
   系统运行状况的监测工具

2. 应收集到的信息
   minor gc多久执行一次,full gc多久执行一次,每次耗时多少?
   高峰期什么状况?
   minor gc时回收的效果如何,survivor的消耗状况如何,每次有
   多少对象会进入old?
   old区在full gc后会消耗多少(简单的memory leak判断方法)
   系统的load、cpu消耗、qps or tps、响应时间
GC Tuning—设定目标
调优的目标是什么呢

 降低Full GC执行频率?

 降低Full GC消耗时间?

 降低Full GC所造成的应用暂停时间?

 降低Minor GC执行频率?

 降低Minor GC消耗时间?

 例如某系统的GC调优目标:
 降低Full GC执行频率的同时,尽可能降低minor GC的执行频率、
 消耗时间以及GC对应用造成的暂停时间。
GC Tuning—尝试调优
根据目标针对性的寻找瓶颈以及制定调优策略

 来说说常见的

 降低Full GC执行频率
  根据前面学习到的Full GC触发时机,寻找到瓶颈
   为什么Full GC执行频率高呢,old经常满?还是old本来占用就高呢?
   old为什么经常满呢?请参见PPT前面的内容...
   是不是因为minor gc后经常有对象进入old呢?为什么?


 注意Java RMI的定时GC触发,可通过:
 -XX:+DisableExplicitGC来禁止;
 或通过 -Dsun.rmi.dgc.server.gcInterval=3600000来控制触发的时间。
GC Tuning—尝试调优
降低Full GC执行频率 – 通常瓶颈

 Old本身占用的就一直高,所以只要稍微放点对象到old,就full了;
    通常原因:缓存的东西太多
         oracle 10g驱动时preparedstatement cache太大

   查找办法,很简单:dump        then mat,bingo!
GC Tuning—尝试调优
降低Full GC执行频率 – 通常瓶颈


 Minor GC后总是有对象不断的进入Old,导致Old不断的满

   通常原因:Survivor太小了
        系统响应太慢、请求量太大、每次请求分配内存多
        分配的对象太大...

   查找办法:dump这时就不是很好用了,需要的是能分析两次
         minor GC之间到底哪些地方分配了内存;

          jstat观察Survivor的消耗状况,-XX:PrintHeapAtGC
           输出GC前后的详细信息;

          系统响应慢那行属于系统优化,不在这里扯;
GC Tuning—尝试调优
降低Full GC执行频率 – 调优策略
 Old本身占用的就一直高

    调优办法
     ① 扩大old大小(减少new或调大heap);
       减少new注意对minor gc的影响并且同时有可能造成full
       gc还是严重;
       调大heap注意full gc的时间的延长,cpu够强悍嘛,os
       32 bit的吗?

      ② 程序优化(去掉一些不必要的缓存)
GC Tuning—尝试调优
降低Full GC执行频率 – 调优策略
 Minor GC后总是有对象不断的进入Old
    前提:这些进入Old的对象在full时大部分都会被回收
     调优办法
        ① 降低Minor GC执行频率(等到那部分再讲)

     ② 让对象尽量在Minor GC中就被回收掉
       放大new、放大survivor、增大TenuringThreshold
       但要注意这些有可能会造成minor gc执行频繁

     ③ 换CMS GC
       Old还没满就回收掉,从而降低Full GC触发的可能
     ④ 程序优化
       提升响应速度、降低每次请求分配的内存
GC Tuning—尝试调优
降低单次Full GC执行时间

 通常原因:
   旧生代太大了...

 通常办法:
   是并行GC吗?
   加点CPU吧
   升级CPU吧
   减小Heap或旧生代吧
GC Tuning—尝试调优
降低Minor GC执行频率

 通常原因:
   每次请求分配的内存多、请求量大

 通常办法:
  扩大heap、扩大新生代、扩大eden,扩大时请综合考虑;
  降低每次请求分配的内存;
  加机器吧,分担点请求量。
GC Tuning—尝试调优
降低Minor GC执行时间

 通常原因:
   新生代太大了
   响应速度太慢了,导致每次Minor GC时存活的对象多

 通常办法:
   减小点新生代吧;
   加点CPU吧、升级CPU吧;
   响应能不能快点呢?
GC Tuning—算命

① 当响应速度下降到多少、或请
  求量上涨到多少时,系统会
  挂掉?

② 参数调整后系统多久会执行一
 次Minor,多久会执行一次
 Full,高峰期会如何?
GC Tuning—不是瞎算的
① 系统的生辰八字
 每次请求平均需要分配多少内存?
 系统的平均响应时间是多少呢?
 请求量是多少、多久一次Minor、Full?

② 先掐指算下
 在现在的参数下,应该是多久一次
 Minor、Full,对比真实状况,做一定
 的偏差;

③ 根据所掌握的知识,就可以判断了
GC Tuning—来算一卦
GC Tuning—总结

        是个复杂过程,按照Tony(GC主要作者的
        说法):GC Tuning is art!

        综合考虑,每个参数的调整都有可能带来
        其他的影响。

        总结来说,提升响应速度、降低每次请求
        分配的内存才是必杀技!
        或者不计成本:64 bit,多个高档CPU、
        大量的内存都上!
实现
Hotspot GC是如何实现的
内存回收
1、通常有两种实现方法:
  1.1 引用计数
      不适合复杂对象引用关系,尤其是有循环依赖的场景;
      需要计数;
      优点是只要计数器降为0,就可被回收。

 1.2 跟踪(有向图Tracing)
     适合于复杂对象引用关系场景,Hotspot采用这种;
     需要到达某个时机后触发执行,并且通常需要暂停应用线程。
     常用算法:Copying、Mark-Sweep、Mark-Compact,算法请
     参见《垃圾回收》这本绝版书。
内存回收
Hotspot从root set开始扫描有引用的对象,并对Reference类型的对象特殊
处理。

root set
1、当前正在执行的线程;
2、全局/静态变量;
3、JVM Handles;
4、JNI Handles;
内存回收
如何暂停应用线程?

 safepoint first: 检测某内存页是否可读的指令

 先想想:只有会改变引用关系的地方才需要暂停,否则没必要,因此对于
 正在native中执行的线程,JVM是不管的,而当native代码需要改变引用
 关系时,又回到了hotspot java部分的代码,而在那些代码上是会有
 safepoint的。

 代码编译时会在某些部分生成safepoint,当需要GC时,GC会向core vm
 提交暂停应用线程的请求,core vm会将safepoint检测的内存页置为
 不可读状态,当解释执行或JIT编译执行到safepoint时,发现内存页不可
 读,于是就挂起了。
新生代可用GC
 串行GC                   并行回收GC
              并行GC
 (Serial                 (Parallel
             (ParNew)
Copying)                Scavenge)
新生代可用GC
在分配时均采用连续空间和bump the pointer的方式。




    A   B   C   D
新生代可用GC
在回收时均采用如下策略:

 扫描新生代,找出其中
 活的对象;
 基于Copying算法回收,
 新生代中活的对象在回收
 时会根据一定的规则晋升
 到旧生代。
新生代可用GC
由于只扫描新生代,如旧生代的对象引用了新生代的,怎么办?

 在给对象赋引用时,会经过一个write barrier;

 检查是否为旧生代引用新生代,如为则记录到remember set中;

 在minor gc时,remember set指向的新生代对象也作为root set。
新生代可用GC—串行
完整内存分配策略
1、首先在tlab上尝试分配;
2、检查是否需要在new上分配,如需要分配的大小小于
  PretenureSizeThreshold,则在eden上进行分配,分配成功则返回,
  分配失败则继续;
3、检查是否需要尝试在旧生代上分配,如需要,则遍历所有代,并检查是
  否可在该代上分配,如可以则进行分配;如不需要在旧生代上尝试分配,
  那么则检查是否可以在eden上分配,如可以则分配,
  否则则尝试在old上分配;
4、根据策略决定执行新生代GC或Full GC,执行full gc时不清除soft Ref;
5、如需要分配的大小大于PretenureSizeThreshold,尝试在old上分配,
  否则尝试在eden上分配;
6、尝试扩大堆并分配;
7、执行full gc,并清除所有soft Ref,按步骤5继续尝试分配。
新生代可用GC—串行
完整内存回收策略

1、检查to是否为空,不为空返回false;
2、检查old剩余空间是否大于当前eden+from已用的大小,如大于则返回true,
  如小于且HandlePromotionFailure为true,则检查剩余空间是否大于之前每次
  minor gc晋级到old的平均大小,如大于返回true,如小于返回false。
3、如上面的结果为false,则执行full gc,如上面的结果为true,执行下面的步
  骤;
4、扫描引用关系,将活的对象copy到to space,如对象在minor gc中的存活次数
  超过tenuring_threshold或分配失败,则往旧生代复制,如仍然复制失败,则
  取决于HandlePromotionFailure,如不需要处理,直接抛出OOM,并退出
  vm,如需处理,则保持这些新生代对象不动;
新生代可用GC—ParNew
内存分配策略和串行方式完全相同。
新生代可用GC—ParNew
内存回收策略

策略和串行相同,只是回收转为了多线程方式。
新生代可用GC—PS
完整内存分配策略
 1、先在TLAB上分配,分配失败则直接在eden上分配;
  2、当eden上分配失败时,检查需要分配的大小是否 >= eden space
    的一半,如是,则直接在旧生代分配;
  3、如分配仍然失败,且gc已超过频率,则抛出OOM;
  4、进入基本分配策略失败的模式;
  5、执行PS GC,在eden上分配;
  6、执行非最大压缩的full gc,在eden上分配;
  7、在旧生代上分配;
  8、执行最大压缩full gc,在eden上分配;
  9、在旧生代上分配;
 10、如还失败,回到2。

最悲惨的情况,分配触发多次PS GC和多次Full GC,直到OOM。
新生代可用GC—PS
完整内存回收策略
1、如gc所执行的时间超过,直接结束;
2、先调用invoke_nopolicy
 2.1 先检查是不是要尝试scavenge;
   2.1.1 to space必须为空,如不为空,则返回false;
   2.1.2 获取之前所有minor gc晋级到old的平均大小,并对比目前
          eden+from已使用的大小,取更小的一个值,如old剩余空间
        小于此值,则返回false,如大于则返回true;
 2.2 如不需要尝试scavenge,则返回false,否则继续;
 2.3 多线程扫描活的对象,并基于copying算法回收,回收时相应的晋升对象到旧生代;
 2.4 如UseAdaptiveSizePolicy,那么重新计算to space和
     tenuringThreshold的值,并调整。
3、如invoke_nopolicy返回的是false,或之前所有minor gc晋级到old的
   平均大小 > 旧生代的剩余空间,那么继续下面的步骤,否则结束;
4、如UseParallelOldGC,则执行PSParallelCompact,如不是
   UseParallelOldGC,则执行PSMarkSweep。
旧生代可用的GC
  串行GC          并行 MSC GC        并行 Compacting GC       并发GC
(Serial MSC)   (Parallel MSC)   (Parallel Compacting)   (CMS)
旧生代可用GC—串行
内存分配策略

1、不支持TLAB;

2、bump pointer的分配方式。
旧生代可用GC—串行
内存回收策略

1、基于Mark Sweep Compact实现;
2、如CollectGen0First为true(默认为false),则先执行minor GC;

回收过程分为四个阶段完成:
1、标记哪些对象是活的;
2、计算新的地址;
3、更新指针指向新的地址;
4、将对象移到新的地址。
旧生代可用GC—并行MSC
内存分配策略

1、在旧生代上按照bump pointer机制进行分配;

2、分配不了的情况下尝试等待几毫秒(通过以下参数设置)后再分配;
  GCExpandToAllocateDelayMillis,默认为0

3、再分配不了就返回NULL。
旧生代可用GC—并行MSC
内存回收基于Mark Sweep Compact实现。

四个阶段:
1、标记活的对象;
2、计算这些活的对象新的目标地址;
3、更新指针指向新的地址;
4、移动对象到新的地址。
旧生代可用GC—并行Compacting
内存分配策略和并行MS相同。
旧生代可用GC—并行Compacting
内存回收基于Mark Compact实现,图示如下:
旧生代可用GC—并发
内存分配策略

1、首先要拿到freelist锁;
2、找到可以容纳下对象大小的chunk,然后分配;
3、如目前正在marking阶段,那么将此新分配的对象标识为活的。
旧生代可用GC—并发
1. 系统启动时在后台启动一个CMS线程,定时检查是否需要触发CMS GC

2. 基于Mark-Sweep实现;
   Initial Marking(Stop-the-world)
   Concurrent Marking
   PreClean(Sun HotSpot 1.5后引入的优化步骤)
   Final Marking (Stop-the-world)
   Concurrent Sweeping
旧生代可用GC—并发
1. Initial Marking(Stop-the-world)
      mark下root set直接引用的对象

2. Concurrent Marking
     并发标识上面mark出来的对象的引用;
     Mod Union Table
       Minor GC同时进行,有可能会导致旧生代引用的对象关系改变
     Card Table
       旧生代中的对象引用关系也有可能改变
旧生代可用GC—并发
3. Preclean
      重新扫描上一步过程中新创建的对象和引用关系改变了的对象的引
      用关系;
      此步什么时候执行,以及执行到什么时候再触发后续动作,取决于
      两个值:-XX: CMSScheduleRemarkEdenSizeThreshold、
            -XX: CMSScheduleRemarkEdenPenetration
      第一个值默认为2,第二个值默认为50%,代表着当eden space
      使用超过2M时,执行此步,当使用超过50%时,触发remark;

    上面这个步骤有些时候有可能会引发bug,有对象需要在old分配
    空间,但由于remark总是没执行,导致old空间不足,默认此步的
    超时时间为5秒,可通过-XX: CMSMaxAbortablePrecleanTime
    设置,单位为毫秒。
旧生代可用GC—并发
4. Final Marking(Stop-the-world)
   处理Mod Union Table和Card Table中dirty的对象,重新mark。

5. Concurrent Sweeping
   并发回收。
旧生代可用GC—并发
优缺点

1. 大部分时候和应用并发进行,因此只会造成很短的暂停时间;

2. 浮动垃圾,没办法,so内存空间要稍微大一点;

3. 内存碎片,-XX:+UseCMSCompactAtFullCollection 来解决;

4. 争抢CPU,这GC方式就这样;

5. 多次remark,所以总的gc时间会比并行的长;

6. 内存分配,free list方式,so性能稍差,对minor GC会有一点影响;

7. 和应用并发,有可能分配和回收同时,产生竞争,引入了锁,JVM分配
   优先。
旧生代可用GC—并发
  浮动垃圾 产生于card table
References
1、GC Tuning in the Hotspot
2、Our Collectors
3、CMS GC
4、why now
5、JDK 6.0 gc tuning
6、memory management in hotspot whitepaper
7、JVM内存管理和垃圾回收

Sun jdk-1.6-gc