GC日志内容说明
GC与GC日志说明
相关代码
/**
* Survivor区放不下存活对象,部分对象进入老年代
* -XX:NewSize=10m -XX:MaxNewSize=10m -Xms20m -Xmx20m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
* -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=10m
*/
public class JvmDemo {
public static void main(String[] args) throws InterruptedException {
byte[] bytes1 = new byte[1024 * 1024];//1m
bytes1 = new byte[1024 * 1024];//1m
bytes1 = new byte[1024 * 1024];//1m
//直接分配一个5m的对象,由于eden区只有8m,之前已经分配了3m再加上一些未知对象也会占据一定的内存空间,此时必然会引起新生代gc
byte[] bytes3 = new byte[5 * 1024 * 1024];//5m
}
}
GC日志
0.161: [GC (Allocation Failure) 0.161: [ParNew: 5753K->890K(9216K), 0.0014072 secs] 5753K->1916K(19456K), 0.0014761 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 9216K, used 6176K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 64% used [0x00000000fec00000, 0x00000000ff129a40, 0x00000000ff400000)
from space 1024K, 86% used [0x00000000ff500000, 0x00000000ff5de970, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
concurrent mark-sweep generation total 10240K, used 1026K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 3530K, capacity 4504K, committed 4864K, reserved 1056768K
class space used 393K, capacity 396K, committed 512K, reserved 1048576K
日志解读
0.161: [GC (Allocation Failure) 0.161: [ParNew: 5753K->890K(9216K), 0.0014072 secs] 5753K->1916K(19456K), 0.0014761 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
GC
表明进行了一次垃圾回收,前面没有Full修饰,表明这是一次Minor GC ,注意它不表示只GC新生代,并且现有的不管是新生代还是老年代都会STW。
Allocation Failure
表明本次引起GC的原因是因为在年轻代中没有足够的空间能够存储新的数据了。
ParNew
表明本次GC发生在年轻代并且使用的是ParNew垃圾收集器。ParNew是一个Serial收集器的多线程版本,会使用多个CPU和线程完成垃圾收集工作(默认使用的线程数和CPU数相同,可以使用-XX:ParallelGCThreads参数限制)。该收集器采用复制算法回收内存,期间会停止其他工作线程,即Stop The World。
5753K->890K(9216K), 0.0014072 secs
单位是KB
前三个参数分别为:GC前该内存区域(这里是年轻代)使用容量,GC后该内存区域使用容量,该内存区域总容量。
0.0014072 secs 是该内存区域GC耗时,单位是秒
5753K->1916K(19456K), 0.0014761 secs
前三个参数分别为:堆区垃圾回收前的大小,堆区垃圾回收后的大小,堆区总大小。
[Times: user=0.00 sys=0.00, real=0.00 secs]:
分别表示用户态耗时,内核态耗时和总耗时
结论
分析下可以得出结论:
GC新生代减少了5753 - 890 = 4863K
Heap区总共减少了5753 - 1916 = 3837K
4863 – 3837 = 1026K,说明该次共有1026内存从年轻代移到了老年代。
我们限制新生代的大小为10M,方法中有两个强引用,总共占用6M的空间,而回收后ParNew占用6M多。5M的对象并没有分配到老年代中。
相关代码2
/**
* Survivor区放不下存活对象,部分对象进入老年代
* -XX:NewSize=10m -XX:MaxNewSize=10m -Xms20m -Xmx20m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
* -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=10m
*/
public class JvmDemo {
public static void main(String[] args) throws InterruptedException {
byte[] bytes1 = new byte[1024 * 1024];//1m
bytes1 = new byte[1024 * 1024];//1m
bytes1 = new byte[1024 * 1024];//1m
byte[] bytes0 = new byte[128 * 1024];//128kb
//直接分配一个5m的对象,由于eden区只有8m,之前已经分配了3m再加上一些未知对象也会占据一定的内存空间,此时必然会引起新生代gc
byte[] bytes3 = new byte[5 * 1024 * 1024];//5m
}
}
gc日志如下:
0.171: [GC (Allocation Failure) 0.171: [ParNew: 5881K->1022K(9216K), 0.0012652 secs] 5881K->2048K(19456K), 0.0013266 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 9216K, used 6308K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 64% used [0x00000000fec00000, 0x00000000ff129a10, 0x00000000ff400000)
from space 1024K, 99% used [0x00000000ff500000, 0x00000000ff5ff8f0, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
concurrent mark-sweep generation total 10240K, used 1026K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 3530K, capacity 4504K, committed 4864K, reserved 1056768K
class space used 393K, capacity 396K, committed 512K, reserved 1048576K
与上例比较发现,我们只是新建了一个128K的对象。根据GC信息看,老年代依然占据着1026K的内存空间。但是from区的内存被占用比例从86%升到了99%,这对比下来就很直观了,128k的对象去了哪里?肯定是from区,因为老年代被占用的内存大小根本没变。
如果Survivor区放不下存活对象,存活对象并不是全都进入老年代,而是部分对象进入老年代。
相关代码3
/**
* Survivor区放不下存活对象,部分对象进入老年代
* -XX:NewSize=10m -XX:MaxNewSize=10m -Xms20m -Xmx20m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
* -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=8m
*/
public class JvmDemo2 {
public static void main(String[] args) throws InterruptedException {
byte[] data0 = new byte[8 * 1024 * 1024];
data0 = null;
byte[] data = null;
//请求4m
for (int i = 0; i < 4; i++) {
//每个请求10m
data = new byte[1024 * 1024];
}
data = null;
byte[] data1 = new byte[5 * 1024 * 1024];
byte[] data2 = new byte[1024 * 1024];
byte[] data3 = new byte[1024 * 1024];
data3 = new byte[1024 * 1024];
data3 = new byte[1024 * 1024];
}
}
相关的GC日志为:
0.163: [GC (Allocation Failure) 0.163: [ParNew: 6777K->850K(9216K), 0.0007905 secs] 14969K->9042K(19456K), 0.0008434 secs] [Times: user=0.02 sys=0.01, real=0.00 secs]
0.164: [GC (Allocation Failure) 0.164: [ParNew (promotion failed): 8266K->7790K(9216K), 0.0007650 secs]0.165: [CMS: 8218K->7922K(10240K), 0.0028139 secs] 16458K->7922K(19456K), [Metaspace: 3538K->3538K(1056768K)], 0.0036178 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.168: [GC (CMS Initial Mark) [1 CMS-initial-mark: 7922K(10240K)] 8946K(19456K), 0.0002572 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.168: [CMS-concurrent-mark-start]
Heap
par new generation total 9216K, used 2449K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 29% used [0x00000000fec00000, 0x00000000fee64460, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
concurrent mark-sweep generation total 10240K, used 7922K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 3545K, capacity 4536K, committed 4864K, reserved 1056768K
class space used 395K, capacity 428K, committed 512K, reserved 1048576K
promotion failed(晋升失败)
最后存活应该是7M
新生代gc后,如果触发了老年代gc,,即使survivor区放的下部分存活对象,对象也会全部进入老年代。
感觉理论支撑应该是:当 Survivor 空间中相同年龄所有对象的大小总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,而不需要达到默认的分代年龄。
引用
https://blog.csdn.net/weixin_38106322/article/details/109122814