对《假笨说-又抓了一个导致频繁GC的鬼–数组动态扩容》实践
1、本文根据此文章进行操作,学习jvm相关的知识:假笨说-又抓了一个导致频繁GC的鬼–数组动态扩容
2、基于jdk1.7实践
1、用到的jvm参数:
- -Xmx550M -Xms550M -Xmn200M heap堆最大550m,初始heap大小为550m,最大新生代大小为200m;
- UseConcMarkSweepGC :This flag is needed to activate the CMS Collector in the first place. By default, HotSpot uses the Throughput Collector instead.
- -XX:+UseCMSInitiatingOccupancyOnly :不基于垃圾收集器收集的状况来选择使用收集器,而是直接使用cms收集器
- -XX:CMSInitiatingOccupancyFraction=80:当老年代的内存使用站80%时,触发老年代回收;
- -XX:+PrintGC 打印gc日志
- -XX:+PrintGCDetails 打印gc详细日志
- -XX:+PrintGCTimeStamps :gc的时刻
- -XX:+PrintGCApplicationStoppedTime :打印stop-the-word消耗的时间
- -XX:+CMSScavengeBeforeRemark :
2、 程序
/**
*-Xmx550M -Xms550M -Xmn200M
-XX:+UseConcMarkSweepGC
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=80
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationStoppedTime
-XX:+CMSScavengeBeforeRemark
* @author Rail
*/
public class Main {
public static void main(String[] args){
System.out.println("hi");
allocateMemory();
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void allocateMemory() {
List<byte[]> list = new ArrayList<byte[]>();
int size = 480 * 1024 * 1024;
System.out.println(size);
int len = size / (20 * 1024);
System.out.println("len = "+len);
for(int i = 0; i < len; i++){
byte[] bytes = new byte[20*1024];
list.add(bytes);
/*if(i == 23760){
System.out.println("waiting");
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
if(i > 15740){
System.out.println("waiting 1s");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i+" "+20*(i + 1)+"kb");
}
}
}
3、内存分配过程
- 每次在新生代分配20kb的内存,约8000次(160mb)后,触发parnew,因为空闲不能容纳160mb,触发老年代的分配担保,从而新生代内存为20mb,老年代为140mb;
- 继续分配20kb,分配16000次,新生代由20mb变为180mb
- 触发parnew,空闲的sur不能容纳180mb,触发老年代分配担保,从而新生代由180mb变为20mb,老年代为140mb+160mb=300mb;
- 老年代300mb触发cms,因为分配的内存在此时都是不可回收的,cms虽然执行,但是不能缩小老年代的实际使用大小。
继续分配20kb,在24000次,新生代由20mb达到180mb,
按照上面的逻辑是:空闲的sur不能容纳180mb,触发老年代分配担保,因为老年代已经300mb了,最大是350mb,只能分配担保50mb,新生代由180mb减小至150mb左右,然后后续的对象分配在新生代中分配,由150mb开始,从下图打印的信息看,这样解释也行得通:
但是在gc日志中,是老年代由300mb变为350mb,然后新生代才由180mb变为110mb的。上一种说法就不成立了,那cms是可以主动的缩小新生代的大小吗?这是我未解决的疑问。
4、jstat -gc pid 属性说明
- S0C :survivor0的最大内存
- S1C :survivor1的最大内存
- S0U :survivor0的已用内存
- S1U :survivor1的已用内存
- EC :Eden区的最大内存
- EU:Eden区的已用内存
- OC:老年代的可用内存
- OU:老年代的已用内存
- MC:Metaspace(元空间)最大内存
- MU :Metaspace(元空间)已用内存
- CCSC:压缩类空间最大内存
- CCSU:压缩类空间已用内存
- YGC:年轻的gc次数
- YGCT:年轻代gc时间
- FGC:full gc次数
- FGCT:full gc时间
- GCT:总gc时间