理解GC日志
2016-03-24 16:19
323 查看
GC堆
1、GC分为两种:MinorGC、FullGC(或称为MajorGC)。2、MinorGC是发生在新生代中的垃圾收集动作,所采用的是复制算法。
a、新生代几乎是所有Java对象出生的地方,即Java对象申请的内存以及存放都是在这个地方。
b、Java中的大部分对象通常不需长久存活,具有朝生夕灭的性质。
c、当一个对象被判定为“死亡”的时候,GC就有责任来回收掉这部分对象的内存空间。
d、新生代是GC收集垃圾的频繁区域。
e、当对象在Eden(+from)出生后,在经过一次MinorGC后,如果对象还存活,并且能够被另外一块Survivor区域
所容纳(这里应为to区域),则使用复制算法将这些仍然还存活的对象复制到另外一块Survivor区域(即to区域)中,
f、然后清理所使用过的Eden以及Survivor区域(即from区域),并且将这些对象的年龄设置为1,
g、以后对象在Survivor区每熬过一次MinorGC,就将对象的年龄+1,
h、当对象的年龄达到某个值时(默认是15岁,可以通过参数-XX:MaxTenuringThreshold来设定),这些对象就会成为
老年代。
j、但这也不是一定的,对于一些较大的对象(即需要分配一块较大的连续内存空间)则是直接进入到老年代。
3、FullGC是发生在老年代的垃圾收集动作,所采用的是标记-清除算法。
a、老年代里面的对象几乎个个都是在Survivor区域中熬过来的,它们是不会那么容易就“死掉”了的。
b、FullGC发生的次数不会有MinorGC那么频繁,并且做一次FullGC要比进行一次MinorGC的时间更长。
c、标记-清除算法收集垃圾的时候会产生许多的内存碎片(即不连续的内存空间),
d、此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次GC的收集动作。
GC日志1
1、“33.125:”和“100.667:”代表了GC发生的时间,这个数字的含义是从Java虚拟机启动以来经过的秒数。
2、“[GC”和“[FullGC”说明了这次垃圾收集的停顿类型,而不是用来区分新生代GC还是老年代GC的。
“Full”,说明这次GC是发生了Stop-The-World的,例如下面这段新生代收集器ParNew的日志也会出
现“[FullGC”(这一般是因为出现了分配担保失败之类的问题,所以才导致STW)。如果是调用System.gc()方
法所触发的收集,那么在这里将显示“[FullGC(System)”。[FullGC283.736:[ParNew:261599K->261599K(261952K),0.0000288secs]
3、[DefNew”、“[Tenured”、“[Perm”表示GC发生的区域,这里显示的区域名称与使用的GC收集器
是密切相关的,
“[DefNew”:Serial收集器中的新生代名为“DefaultNewGeneration”
“[ParNew”:ParNew收集器,新生代,意为“ParallelNewGeneration”。
“PSYoungGen”:ParallelScavenge收集器
老年代和永久代同理,名称也是由收集器决定的。Tenured表示老年代
4、“3324K->152K(3712K)”是“GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存区域总容量)”。
5、方括号之外“3324K->152K(11904K)”表示“GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量)”。
6、“0.0025925secs”表示该内存区域GC所占用的时间,单位是秒。
7、“[Times:user=0.01sys=0.00,real=0.02secs]”
user:用户态消耗的CPU时间
sys:内核态消耗的CPU时间
real:操作从开始到结束所经过的墙钟时间(WallClockTime)
CPU时间与墙钟时间的区别是,墙钟时间包括各种非运算的等待耗时,例如等待磁盘I/O、等待线程阻塞,而CPU时间不包括这些耗时,但当系统有多CPU或者多核的话,多线程操作会叠加这些CPU时间,所以读者看到user或sys时间超过real时间是完全正常的。
GC日志2
1 2 3 4 5 6 7 8 9 | public static void main(String[] args){ Object obj= new Object(); System.gc(); System.out.println(); obj = new Object(); obj = new Object(); System.gc(); System.out.println(); } |
GC日志3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 1 /** 2 -Xms60m 3 -Xmx60m 4 -Xmn20m 5 6 -XX:SurvivorRatio=8 7 -XX:PermSize=30m 8 -XX:MaxPermSize=30m 9 -XX:+PrintGCDetails 10*/ 11 public static void main(String[] args){ 12 new Test().doTest(); 13 } 14 15 public void doTest(){ 16 Integer new Integer( 1024 * 1024 * 1 ); //单位, 兆(M) 17 byte [] new byte [ 1 * //申请1M大小的内存空间 18 bytes null ; //断开引用链 19 System.gc(); //通知 20 System.out.println(); 21 bytes new byte [ 1 * //重新申请1M大小的内存空间 22 bytes new byte [ 1 * //再次申请1M大小的内存空间 23 System.gc(); 24 System.out.println(); 25 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | [GC[PSYoungGen:1351K->288K(18432K)]1351K->288K(59392K),0.0012389secs][ [FullGC(System)[PSYoungGen:288K->0K(18432K)][PSOldGen:0K->160K(40960K) [GC[PSYoungGen:2703K->1056K(18432K)]2863K->1216K(59392K),0.0008206secs] [FullGC(System)[PSYoungGen:1056K->0K(18432K)][PSOldGen:160K->1184K(40960K) Heap PSYoungGen eden from to PSOldGen object PSPermGen object |
2、新生代=eden+from+to=16+2+2=20M,可见新生代的内存空间确实是按Xmn参数分配得到的。
3、SurvivorRatio=8,因此,eden=8/10的新生代空间=8/10*20=16M。from=to=1/10的新生代空间=1/10*20=2M。
4、堆信息中新生代的total18432K是这样来的:eden+1个survivor=16384K+2048K=18432K,即约为18M。因为jvm每次只是用新生代中的eden和一个survivor,因此新生代实际的可用内存空间大小为所指定的90%。因此可以知道,这里新生代的内存空间指的是新生代可用的总的内存空间,而不是指整个新生代的空间大小。
5、另外,可以看出老年代的内存空间为40960K(约40M),堆大小=新生代+老年代。因此在这里,老年代=堆大小–新生代=60–20=40M。
6、最后,这里还指定了PermSize=30m,PermGen即永久代(方法区),它还有一个名字,叫非堆,主要用来存储由jvm加载的类文件信息、常量、静态变量等。
7、回到doTest()方法中,可以看到代码在第17、21、22这三行中分别申请了一块1M大小的内存空间,并在19和23这两行中分别显式的调用了System.gc()。从控制台打印的信息来看,每次调System.gc(),是先进行MinorGC,然后再进行FullGC。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 1 /** 2 -Xms60m 3 -Xmx60m 4 -Xmn20m 5 6 -XX:SurvivorRatio=8 7 -XX:PermSize=30m 8 -XX:MaxPermSize=30m 9 -XX:+PrintGCDetails 10*/ 11 public static void main(String[] args){ 12 new Test().doTest(); 13 } 14 15 public void doTest(){ 16 Integer new Integer( 1024 * 1024 * 1 ); //单位, 兆(M) 17 byte [] new byte [ 1 * //申请1M大小的内存空间 18 bytes null ; //断开引用链 19 System.gc(); //通知 20 System.out.println(); 21 bytes new byte [ 1 * //重新申请1M大小的内存空间 22 bytes new byte [ 1 * //再次申请1M大小的内存空间 23 System.gc(); 24 System.out.println(); 25 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | [GC[PSYoungGen:1351K->288K(18432K)]1351K->288K(59392K),0.0012389secs][ [FullGC(System)[PSYoungGen:288K->0K(18432K)][PSOldGen:0K->160K(40960K) [GC[PSYoungGen:2703K->1056K(18432K)]2863K->1216K(59392K),0.0008206secs] [FullGC(System)[PSYoungGen:1056K->0K(18432K)][PSOldGen:160K->1184K(40960K) Heap PSYoungGen eden from to PSOldGen object PSPermGen object |
从信息PSYoungGen:1351K->288K,可以知道,在第17行为bytes分配的内存空间已经被回收完成。
引起GC回收这1M内存空间的因素是第18行的bytes=null;bytes为null表明之前申请的那1M大小的内存空间现在已经没有任何引用变量在使用它了,
并且在内存中它处于一种不可到达状态(即没有任何引用链与GCRoots相连)。那么,当MinorGC发生的时候,GC就会来回收掉这部分的内存空间。
9、第19行触发的FullGC收集分析:
在MinorGC的时候,信息显示PSYoungGen:1351K->288K,再看看FullGC中显示的PSYoungGen:288K->0K,可以看出,FullGC后,新生代的内存使用变成0K了
刚刚说到FullGC后,新生代的内存使用从288K变成0K了,那么这288K到底哪去了?难道都被GC当成垃圾回收掉了?当然不是了。我还特意在main方法中new了一个Test类的实例,这里的Test类的实例属于小对象,它应该被分配到新生代内存当中,现在还在调用这个实例的doTest方法呢,GC不可能在这个时候来回收它的。
接着往下看FullGC的信息,会发现一个很有趣的现象,PSOldGen:0K->160K,可以看到,FullGC后,老年代的内存使用从0K变成了160K,想必你已经猜到大概是怎么回事了。当FullGC进行的时候,默认的方式是尽量清空新生代(YoungGen),因此在调System.gc()时,新生代(YoungGen)中存活的对象会提前进入老年代。
10、第23行触发的MinorGC收集分析:
从信息PSYoungGen:2703K->1056K,可以知道,在第21行创建的,大小为1M的数组被GC回收了。在第22行创建的,大小也为1M的数组由于bytes引用变量还在引用它,因此,它暂时未被GC回收。
11、第23行触发的FullGC收集分析:
在MinorGC的时候,信息显示PSYoungGen:2703K->1056K,FullGC中显示的PSYoungGen:1056K->0K,以及PSOldGen:160K->1184K,可以知道,新生代(YoungGen)中存活的对象又提前进入老年代了。
相关文章推荐
- 每天一题(48) - C++实现Singleton模式
- 共享内存
- MD5加密
- 【Numpy】python机器学习包Numpy基础知识学习
- Git 常用命令整理
- 程序里面的system.out.println()输出到其他位置,不输出到tomcat控制台。
- JVM参数配置
- Medusa: Gauges for JavaFX
- SVN中trunk,branches,tags用法详解
- hdu 1060 Leftmost Digit
- lucene 学习日记(一) 源码的导入,及第一个demo的开始
- 自定义msi安装包的执行过程
- AndroidStudio使用常见问题(实时更新)
- DDL语句--创建数据库
- 博客入驻阿里“云栖社区”
- MMC/SD/SDIO的区别
- git fetch的使用
- VR:下一个技术风口
- 使用NuGet加载包,发现加载的dll都是最新版,原来少加了参数[-Version]
- 用Jenkins配置自动化构建