日志查看
常见命令
-verbose:gc在控制台输出GC日志
-Xms20m JVM堆空间初始容量20m
-Xmx20m 堆空间最大20m
-Xmn10m 堆新生代最大内存空间10m
-XX:+PrintGCDetails 在控制台输出GC详细日志
-XX:+SurvivorRatio=8 幸存者 比率=8
当咱们设置新生代最大内存我饿10m 幸存者比例为8时,fromsurvivor 和ToSurvivor 比例相同为1:1
(如果不能整除就取近似值) 三者比例为8:1:1
java -XX:+PrintCommandLineFlags -version 打印java的版本号 jvm的版本号 当前jvm默认的启动参数
-XX:PretenureSizeThreshold=4194304(1024*1024*4 4m)(字节B为单位)
表示当新生代对象占据的字节大于指定的字节(4M)就不会在新生代创建直接在老年代创建
-XX:+UseParallelGC
虚拟机运行时的默认值 默认使用Parallel Scavenge+Serial Old 的垃圾收集器来进行内存回收
-XX:+UseSerialGC 虚拟机使用Serial 收集器进行垃圾回收
-XX:MaxTenuringThreshold=5的作用:在可以自动调节对象晋升(Promote)到老年代阈值的GC中,
-XX:+PrintTenuringDistribution的作用打印不同年龄段的对象字节
-XX:TargetSurvivorRatio=60 当survivor空间大于百分之60 就得重新计算晋升老年代的阈值 就不会使用
-XX:MaxTenuringThreshold=5 的值
-XX:+PrintGCDateStamps 打印当前执行GC的一个时间戳
-XX:+UseConcMarkSweepGC 使用CMS垃圾收集器(并发的 标记-清除算法) 该收集器只针对老年代
-XX:+UseParNewGC 使用ParNew收集器(并发 复制算法) 该收集器只针对新生代
示例一
/**
* -verbose:gc 在控制台输出GC日志
* -Xms20m JVM堆空间初始容量20m
* -Xmx20m 堆空间最大20m
* -Xmn10m 堆新生代最大内存空间10m
* -XX:+PrintGCDetails 在控制台输出GC详细日志
* -XX:+SurvivorRatio=8 幸存者 比率=8
* 当咱们设置新生代最大内存我饿10m 幸存者比例为8时,fromsurvivor 和ToSurvivor 比例相同为1:1(如果不能整除就取近似值) 三者比例为8:1:1
*
* Hostpot jdk1.8版本 虚拟机新生代老年代 默认使用
* PSYoungGen : Parallel Scavenge (新生代的垃圾收集器)
* ParOldGen : Parallel Old(老年代垃圾收集器)
*/
public class MyTest1 {
public static void main(String[] args) {
int size = 1024 * 1024;
byte[] myAllocl1 = new byte[2 * size];
byte[] myAllocl2 = new byte[2 * size];
byte[] myAllocl5 = new byte[2 * size];
byte[] myAllocl6 = new byte[2 * size];
byte[] myAllocl9 = new byte[2 * size];
System.out.println("hello world"); //没加之前 原样输出
}
}
加入JVM参数打印的日志如下
[GC (Allocation Failure) [PSYoungGen: 7412K->1019K(9216K)] 7412K->3810K(19456K), 0.0022353 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 7319K->1019K(9216K)] 10110K->9970K(19456K), 0.0025766 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//对整个堆空间进行回收
[Full GC (Ergonomics) [PSYoungGen: 1019K->0K(9216K)] [ParOldGen: 8951K->9727K(10240K)] 9970K->9727K(19456K), [Metaspace: 3509K->3509K(1056768K)], 0.0075662 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
hello world
Heap
PSYoungGen total 9216K, used 6365K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 77% used [0x00000000ff600000,0x00000000ffc375c8,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 10240K, used 9727K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 94% used [0x00000000fec00000,0x00000000ff57fce0,0x00000000ff600000)
Metaspace used 3519K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 378K, capacity 388K, committed 512K, reserved 1048576K
问题
当程序如下时
此时会产生Full GC
当我们将程序改动 对象内存空间增加
然而此时并没有触发Full GC
原因:当新生代内存空间不足以给新创建的对象开辟空间则直接在老年代为其开辟了内存(如: byte[] myAllocl9 = new byte[5 * size]😉 所以没触发Full GC。
此时老年代的内存大小 = 新生代释放的容量 - 指定总堆空间释放的容量 + 5×1024
=7202-995-(7202-3780)+5*1024 =7905K 刚好等于上图中老年代内存使用的内存空间大小
查看JVM启动参数
java -XX:+PrintCommandLineFlags -version 打印java的版本号 jvm的版本号 当前jvm默认的启动参数
C:\Users\Admin>java -XX:+PrintCommandLineFlags -version
-Xms 等价于 -XX:InitialHeapSize 代表初始化堆内存大小
-Xmx 等价于 -XX:MaxHeapSize 代表最大的堆内存大小
-XX:+PrintCommandLineFlags 打印当前jvm默认的启动参数
-XX:+UseCompressedClassPointers 使用压缩的内指针 节省空间
-XX:+UseCompressedOops 从32虚拟机迁移到64位虚拟机 进行指针压缩处理
-XX:+UseParallelGC 虚拟机运行时的默认值 默认使用Parallel Scavenge+Serial Old 的垃圾收集器来进行内存回收
-XX:InitialHeapSize=266317120 -XX:MaxHeapSize=4261073920
-XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers
-XX:+UseCompressedOops -XX:-UseLargePagesInd
ividualAllocation -XX:+UseParallelGC
java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)
示例二
/**
* java -XX:+PrintCommandLineFlags -version 打印java的版本号 jvm的版本号 当前jvm默认的启动参数
* <p>
* 1KB=1024B;1MB=1024KB=1024×1024B。
* 1B(byte,字节)= 8 bit
* -XX:PretenureSizeThreshold=4194304(1024*1024*4 4m)(字节B为单位) 表示当新生代对象占据的字节大于指定的字节(4M)就不会在新生代创建直接在老年代创建
* ——jdk1.8默认垃圾收集器也可以(当新生代内存不足,对象不会拆分成两半一半放到新生代一半放到老年代)
* <p>
* -XX:+UseParallelGC 虚拟机运行时的默认值 默认使用Parallel Scavenge+Serial Old 的垃圾收集器来进行内存回收
* -XX:+UseSerialGC 虚拟机使用Serial 收集器进行垃圾回收
*/
public class MyTest2 {
public static void main(String[] args) {
int size = 1024 * 1024;
byte[] myAlloc = new byte[5 * size];
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*
C:\Users\Admin>java -XX:+PrintCommandLineFlags -version
-Xms 等价于 -XX:InitialHeapSize 代表初始化堆内存大小
-Xmx 等价于 -XX:MaxHeapSize 代表最大的堆内存大小
-XX:+PrintCommandLineFlags 打印当前jvm默认的启动参数
-XX:+UseCompressedClassPointers 使用压缩的内指针 节省空间
-XX:+UseCompressedOops 从32虚拟机迁移到64位虚拟机 进行指针压缩处理
-XX:+UseParallelGC 虚拟机运行时的默认值 默认使用Parallel Scavenge+Serial Old 的垃圾收集器来进行内存回收
-XX:InitialHeapSize=266317120 -XX:MaxHeapSize=4261073920
-XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers
-XX:+UseCompressedOops -XX:-UseLargePagesInd
ividualAllocation -XX:+UseParallelGC
java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)
*/
/**
* 当在参数 设置-XX:+UseSerialGC 虚拟机使用Serial 收集器进行垃圾回收
* 此时日志打印
*/
/*
Heap
def new generation total 9216K, used 7534K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 91% used [0x00000000fec00000, 0x00000000ff35bac8, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
Metaspace used 3508K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 378K, capacity 388K, committed 512K, reserved 1048576K
*/
/***
* 当byte[] myAlloc = new byte[10*size];时 日志打印
*/
/*
[GC (Allocation Failure) [PSYoungGen: 5322K->1003K(9216K)] 5322K->1745K(19456K), 0.0020518 secs] [Times: user=0.03 sys=0.03, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1003K->1019K(9216K)] 1745K->1793K(19456K), 0.0013766 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 1019K->0K(9216K)] [ParOldGen: 774K->1694K(10240K)] 1793K->1694K(19456K), [Metaspace: 3416K->3416K(1056768K)], 0.0060474 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(9216K)] 1694K->1694K(19456K), 0.0029145 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
//当Full GC 完 老年代还是满的就会抛出 Java heap space 堆内存溢出
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(9216K)] [ParOldGen: 1694K->1676K(10240K)] 1694K->1676K(19456K), [Metaspace: 3416K->3416K(1056768K)], 0.0103934 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 9216K, used 402K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 4% used [0x00000000ff600000,0x00000000ff6648e0,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 10240K, used 1676K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 16% used [0x00000000fec00000,0x00000000feda3090,0x00000000ff600000)
Metaspace used 3517K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 378K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.example.demo.com.jvm.gc.MyTest2.main(MyTest2.java:19)
*/
使用jvisualVM 和jmc 查看当前系统堆使用情况
示例三
/**
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:+PrintTenuringDistribution *
* -XX:MaxTenuringThreshold=5的作用:在可以自动调节对象晋升(Promote)到老年代阈值的GC中,
* 设置该阈值的最大值 该参数的默认值为15,CMS中默认值是6,G1中默认是15(在JVM中,该数值是由4个bit来表示的,所有最大值 1111 即 15)
* (理想情况:没回收的对象每GC一次(初始1) 年龄就+1 当年龄>设置值5 就晋升, 现实情况:Jvm可能存在年龄为2的时候就晋升 但不会超过5)
*
* Eden经历多次GC后,存活的对象会在From Survivor 和To Survivor 中来回存放,而这里面的一个前提则是这两个空间有足够的大小来存放这些数据,在GC算法中,
* 会计算每个对象年龄的大小,如果达到某个年龄后发现总大小已经大于了Survivor空间的50%,那么这时就需要(自动)调整阈值,不能再继续等到默认的15次GC后才完成晋升,
* 因为这样会导致Survivor空间不足,所以需要调整阈值,让这些存活的对象尽快完成晋升。
*
* -XX:+PrintTenuringDistribution的作用打印不同年龄段的对象字节
*/
public class MyTest3 {
public static void main(String[] args) {
int size = 1024 * 1024;
byte[] myAllocl1 = new byte[2 * size];
byte[] myAllocl2 = new byte[2 * size];
byte[] myAllocl5 = new byte[2 * size];
byte[] myAllocl3 = new byte[2 * size];
System.out.println("hello world");
}
}
/*
-XX:InitialHeapSize=20971520 -XX:InitialTenuringThreshold=5 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=5 -XX:NewSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
[GC (Allocation Failure)
survivor size 1048576 bytes = 1m 动态计算出来的 5(可能发生变化 <=5)(设置的阈值5)
Desired survivor size 1048576 bytes, new threshold 5 (max 5)
[PSYoungGen: 7370K->1016K(9216K)] 7370K->3877K(19456K), 0.0033477 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
hello world
Heap
PSYoungGen total 9216K, used 7482K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 79% used [0x00000000ff600000,0x00000000ffc651a0,0x00000000ffe00000)
from space 1024K, 99% used [0x00000000ffe00000,0x00000000ffefe010,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 10240K, used 2861K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 27% used [0x00000000fec00000,0x00000000feecb7b8,0x00000000ff600000)
Metaspace used 3476K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 372K, capacity 388K, committed 512K, reserved 1048576K
*/
多次GCEden空间年龄变化
/**
-verbose:gc -Xms200M -Xmn50M -XX:TargetSurvivorRatio=60 -XX:+PrintTenuringDistribution -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:MaxTenuringThreshold=3 *
*
* -XX:TargetSurvivorRatio=60 当survivor空间大于百分之60 就得重新计算晋升老年代的阈值 就不会使用-XX:MaxTenuringThreshold=5 的值
* -XX:+PrintGCDateStamps 打印当前执行GC的一个时间戳
* -XX:+UseConcMarkSweepGC 使用CMS垃圾收集器(并发的 标记-清除算法) 该收集器只针对老年代
* -XX:+UseParNewGC 使用ParNew收集器(并发 复制算法) 该收集器只针对新生代
*
*/
public class MyTest4 {
public static void main(String[] args) throws InterruptedException {
byte[] byte_1 = new byte[512 * 1024];
byte[] byte_2 = new byte[512 * 1024];
// 当将23456去掉直接将byte_3 4 5 创建 会发现 并没有将其放置到老年代 而是等下次GC才执行上次GC的操作
// byte[] byte_3 = new byte[1024*1024]; //此时每个都存放在新生代
// byte[] byte_4 = new byte[1024*1024];
// byte[] byte_5 = new byte[1024*1024];
myGc();
Thread.sleep(1000);
System.out.println("11111111");
myGc();
Thread.sleep(1000);
System.out.println("222222");
myGc();
Thread.sleep(1000);
System.out.println("33333333");
myGc();
Thread.sleep(1000);
System.out.println("4444444444");
byte[] byte_3 = new byte[1024*1024]; //此时每个都存放在新生代
byte[] byte_4 = new byte[1024*1024];
byte[] byte_5 = new byte[1024*1024];
myGc();
Thread.sleep(1000);
System.out.println("55555555"); // 此时还是放置在新生代 下次GC之前会将 新生代的所有的放置在新生的晋升至老年代 其中没有引用的
myGc();
Thread.sleep(1000);
System.out.println("66666666");
System.out.println("hello world");
}
private static void myGc() {
for (int i = 0; i < 40; i++) {
/*
每次循环完 byteArray 栈帧中局部变量就会被回收 详情看byteCode 的MyTest4 中进行了讲解
生成的对象在堆中等待垃圾回收器回收
*/
byte[] byteArray = new byte[1024 * 1024];
}
}
}
/*
-verbose:gc -Xms200M -Xmn50M -XX:TargetSurvivorRatio=60 -XX:+PrintTenuringDistribution -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:MaxTenuringThreshold=3
//时间戳 [GC(分配失败) 时间戳:[新生代期望 survivor size 为3m时百分之60时就将其晋升到老年代 (未设置时CMS的新生代按照8:1:1(survivor为5)) 当前阈值3(最大年龄3 晋升)
2020-10-18T15:01:22.339+0800: [GC (Allocation Failure) 2020-10-18T15:01:22.339+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
//没有回收的
年龄为1 的占据字节 使用字节
byte_1 和 byte_2
- age 1: 2871528 bytes, 2871528 total
GC前占用内存->GC后(50M可使用的45M 40:5:5) GC前总占用内存->GC后(总堆200 可用内存195)
: 40363K->2839K(46080K), 0.0026850 secs] 40363K->2839K(199680K), 0.0027268 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
11111111
2020-10-18T15:01:23.344+0800: [GC (Allocation Failure) 2020-10-18T15:01:23.344+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age 1: 232 bytes, 232 total
//年龄为2的
- age 2: 2477264 bytes, 2477496 total
: 43569K->2642K(46080K), 0.0017492 secs] 43569K->2642K(199680K), 0.0017725 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
222222
2020-10-18T15:01:24.349+0800: [GC (Allocation Failure) 2020-10-18T15:01:24.349+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age 1: 56 bytes, 56 total
- age 2: 232 bytes, 288 total
- age 3: 2456144 bytes, 2456432 total
: 43169K->2627K(46080K), 0.0016696 secs] 43169K->2627K(199680K), 0.0017119 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
33333333
2020-10-18T15:01:25.355+0800: [GC (Allocation Failure) 2020-10-18T15:01:25.355+0800: [ParNew
//当地三次GC完 上次年龄为3的都直接晋升到老年代
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age 1: 56 bytes, 56 total
- age 2: 56 bytes, 112 total
- age 3: 232 bytes, 344 total
: 43356K->675K(46080K), 0.0120055 secs] 43356K->3096K(199680K), 0.0120649 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
4444444444
//第五次时新增了3M 超过阈值(main方法没执行完一直引用) 此时还是放置在新生代age1 下次GC之前会将 新生代的所有的放置在新生的晋升至老年代 其中没有引用的
//此时new threshold 1 变成了1(取存活对象年龄的最小值 和阈值3 两者直接的最小值 所以取得1)所以此时 新生代年龄1次及其以上的全都晋升到老年代
2020-10-18T15:01:26.371+0800: [GC (Allocation Failure) 2020-10-18T15:01:26.371+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 1 (max 3)
//新加的那3M
- age 1: 3487560 bytes, 3487560 total
- age 2: 56 bytes, 3487616 total
- age 3: 56 bytes, 3487672 total
: 41408K->3537K(46080K), 0.0019821 secs] 43829K->5958K(199680K), 0.0020481 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
55555555
//方法执行完之后阈值又变成3 每次GC都会计算它的阈值(此时之前就会执行上次GC的操作)
2020-10-18T15:01:27.378+0800: [GC (Allocation Failure) 2020-10-18T15:01:27.378+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
//新涌入的对象经过回收重新填入survivor当中
- age 1: 56 bytes, 56 total
: 44257K->83K(46080K), 0.0044580 secs] 46678K->5934K(199680K), 0.0045075 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
66666666
hello world
Heap
par new generation total 46080K, used 20740K [0x00000006c2000000, 0x00000006c5200000, 0x00000006c5200000)
eden space 40960K, 50% used [0x00000006c2000000, 0x00000006c342c480, 0x00000006c4800000)
from space 5120K, 1% used [0x00000006c4800000, 0x00000006c4814e80, 0x00000006c4d00000)
to space 5120K, 0% used [0x00000006c4d00000, 0x00000006c4d00000, 0x00000006c5200000)
//此时老年代存储有main的4个字节 虽然main方法执行完引用丢失 因为没触发GC 所有此时还存在在老年代 等下次GC就会被回收
concurrent mark-sweep generation total 153600K, used 5851K [0x00000006c5200000, 0x00000006ce800000, 0x00000007c0000000)
Metaspace used 4013K, capacity 4568K, committed 4864K, reserved 1056768K
class space used 435K, capacity 460K, committed 512K, reserved 1048576K
*/
总结:当我们设置了survivor最大内存比例,CMS收集器每次回收会先判断阈值 每次收集 存活年龄+1 当survivor空间超过最大内存比例如60 就会晋升到老年代(无引用会被释放掉)值得注意当超过设置的最大值阈值会自动改变值取存活对象年龄的最小值 和阈值 直接的最小值 上例为1。在下次GC前就会将其新生代所有的晋升至老年代(无引用会被释放掉)