Transcript 旧生代可用的GC
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寄
存器
操作数栈
栈帧
JVM方法栈
本地方法栈
JVM方法区
JVM堆
-XX:PermSize –
XX:MaxPermSize
-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
(Serial
Copying)
并行GC
(ParNew)
并行回收GC
(Parallel 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{
public static void main(String[] args) throws Exception{
SerialGCMemoryObject object1=new SerialGCMemoryObject(1);
SerialGCMemoryObject object2=new SerialGCMemoryObject(8);
SerialGCMemoryObject object3=new SerialGCMemoryObject(8);
SerialGCMemoryObject object4=new SerialGCMemoryObject(8);
object2=null;
object3=null;
SerialGCMemoryObject object5=new SerialGCMemoryObject(8);
Thread.sleep(4000);
object2=new SerialGCMemoryObject(8);
object3=new SerialGCMemoryObject(8);
object2=null;
object3=null;
object5=null;
SerialGCMemoryObject object6=new SerialGCMemoryObject(8);
Thread.sleep(5000);
}
}
class SerialGCMemoryObject{
private byte[] bytes=null;
public SerialGCMemoryObject(int multi){
bytes=new byte[1024*256*multi];
}
}
-Xms20M –Xmx20M –
Xmn10M –
XX:+UseSerialGC
-Xms20M –Xmx20M –
Xmn10M –
XX:+UseSerialGC
XX:MaxTenuringThres
hold=1
新生代可用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
(Serial MSC)
并行 MS GC
(Parallel MSC)
并行 Compacting GC
(Parallel Compacting)
我该用哪个呢?
并发GC
(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
常用参数(-Xms –Xmx –Xmn –XX:PermSize –XX:MaxPermSize)
GC方式
新生代可用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的线程数。
旧生代和持久代可用GC
ParNew 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
启动参数
在系统运行到67919.837秒时发生了一次Full GC,日志信息
如下:
◦ -server -Xms1536m -Xmx1536m –Xmn700m
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、细微调整
4、衡量调优
2、设定目标
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
(Serial Copying)
并行GC
(ParNew)
并行回收GC
(Parallel 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
(Serial MSC)
并行 MSC GC
(Parallel MSC)
并行 Compacting GC
(Parallel Compacting)
并发GC
(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内存管理和垃圾回收