您的位置:首页 > 其它

理解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();

}


设置JVM参数为-XX:+PrintGCDetails,使得控制台能够显示GC相关的日志信息,执行上面代码,下面是其中一次执行的结果。











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
-XX:NewRatio=2(若Xms=Xmx,并且设定了Xmn,那么该项配置就不需要配置了)

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
M=
new
Integer(
1024
*
1024
*
1
);
//单位,
兆(M)

17
byte
[]
bytes=
new
byte
[
1
*
M];
//申请1M大小的内存空间

18
bytes
=
null
;
//断开引用链

19
System.gc();
//通知
GC收集垃圾

20
System.out.println();

21
bytes
=
new
byte
[
1
*
M];
//重新申请1M大小的内存空间

22
bytes
=
new
byte
[
1
*
M];
//再次申请1M大小的内存空间

23
System.gc();

24
System.out.println();

25
}


按上面代码中注释的信息设定jvm相关的参数项,并执行程序,下面是一次执行完成控制台打印的结果:

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][
Times:user=0.00sys=0.00,real=0.00secs]

[FullGC(System)[PSYoungGen:288K->0K(18432K)][PSOldGen:0K->160K(40960K)
]288K->160K(59392K)[PSPermGen:2942K->2942K(30720K)],0.0057649secs][Times:user=0.00sys=0.00,real=0.01secs]


[GC[PSYoungGen:2703K->1056K(18432K)]2863K->1216K(59392K),0.0008206secs]
[Times:user=0.00sys=0.00,real=0.00secs]

[FullGC(System)[PSYoungGen:1056K->0K(18432K)][PSOldGen:160K->1184K(40960K)
]1216K->1184K(59392K)[PSPermGen:2951K->2951K(30720K)],0.0052445secs][Times:user=0.02sys=0.00,real=0.01secs]


Heap

PSYoungGen
total18432K,used327K[0x00000000fec00000,0x0000000100000000,0x0000000100000000)

eden
space16384K,2%used[0x00000000fec00000,0x00000000fec51f58,0x00000000ffc00000)

from
space2048K,0%used[0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)

to
space2048K,0%used[0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)

PSOldGen
total40960K,used1184K[0x00000000fc400000,0x00000000fec00000,0x00000000fec00000)

object
space40960K,2%used[0x00000000fc400000,0x00000000fc5281f8,0x00000000fec00000)

PSPermGen
total30720K,used2959K[0x00000000fa600000,0x00000000fc400000,0x00000000fc400000)

object
space30720K,9%used[0x00000000fa600000,0x00000000fa8e3ce0,0x00000000fc400000)


1、堆中新生代的内存空间为18432K(约18M),eden的内存空间为16384K(约16M),from/tosurvivor的内存空间为2048K(约2M)。

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
-XX:NewRatio=2(若Xms=Xmx,并且设定了Xmn,那么该项配置就不需要配置了)

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
M=
new
Integer(
1024
*
1024
*
1
);
//单位,
兆(M)

17
byte
[]
bytes=
new
byte
[
1
*
M];
//申请1M大小的内存空间

18
bytes
=
null
;
//断开引用链

19
System.gc();
//通知
GC收集垃圾

20
System.out.println();

21
bytes
=
new
byte
[
1
*
M];
//重新申请1M大小的内存空间

22
bytes
=
new
byte
[
1
*
M];
//再次申请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][
Times:user=0.00sys=0.00,real=0.00secs]

[FullGC(System)[PSYoungGen:288K->0K(18432K)][PSOldGen:0K->160K(40960K)
]288K->160K(59392K)[PSPermGen:2942K->2942K(30720K)],0.0057649secs][Times:user=0.00sys=0.00,real=0.01secs]


[GC[PSYoungGen:2703K->1056K(18432K)]2863K->1216K(59392K),0.0008206secs]
[Times:user=0.00sys=0.00,real=0.00secs]

[FullGC(System)[PSYoungGen:1056K->0K(18432K)][PSOldGen:160K->1184K(40960K)
]1216K->1184K(59392K)[PSPermGen:2951K->2951K(30720K)],0.0052445secs][Times:user=0.02sys=0.00,real=0.01secs]


Heap

PSYoungGen
total18432K,used327K[0x00000000fec00000,0x0000000100000000,0x0000000100000000)

eden
space16384K,2%used[0x00000000fec00000,0x00000000fec51f58,0x00000000ffc00000)

from
space2048K,0%used[0x00000000ffe00000,0x00000000ffe00000,0x0000000100000000)

to
space2048K,0%used[0x00000000ffc00000,0x00000000ffc00000,0x00000000ffe00000)

PSOldGen
total40960K,used1184K[0x00000000fc400000,0x00000000fec00000,0x00000000fec00000)

object
space40960K,2%used[0x00000000fc400000,0x00000000fc5281f8,0x00000000fec00000)

PSPermGen
total30720K,used2959K[0x00000000fa600000,0x00000000fc400000,0x00000000fc400000)

object
space30720K,9%used[0x00000000fa600000,0x00000000fa8e3ce0,0x00000000fc400000)


8、第19行触发的MinorGC收集分析:

从信息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)中存活的对象又提前进入老年代了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: