触发GC的条件
触发MinorGC 的条件:
Eden区满了
触发Full GC的条件:
Full GC触发条件:
(1)调用System.gc时,JVM并不会马上执行,执行时机不确定的
(2)老年代空间不足
(3)方法去空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小,Eden or Survivor ----> To Survivor 如果不够则通过内存担保措施直接晋升到老年代。
第一个例子
命令行参数
-Xms20m
-Xmx20m
-Xmn10m
-verbose:gc
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
先介绍一下每个参数的含义
- Xms初始化堆内存、
- Xmx 最大堆内存、
- Xmn 是新生代的大小、
- verbose:gc GC 日志中输出详细的GC信息 、
- -XX:+PrintGcDetails打印 GC 细节与发生时间、
- -XX:SurvivorRatio=8 Eden 区域跟Survivor区域的大小比值 Eden:Survivor 8:1 = 8 Eden:From Survivor: To Survivor
测试代码
public class MyGcTest02 {
public static void main(String[] args) {
int size = 1024 * 1024; //1m 大小
byte[] byteArray1 =new byte[2*size];
byte[] byteArray2 =new byte[2*size];
byte[] byteArray3 =new byte[2*size];
byte[] byteArray4 =new byte[2*size];
System.out.println("hello world");
}
}
打印日志如下:
[GC (Allocation Failure) [PSYoungGen: 8141K->776K(9216K)] 8141K->6928K(19456K), 0.0038074 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 776K->0K(9216K)] [ParOldGen: 6152K->6768K(10240K)] 6928K->6768K(19456K), [Metaspace: 3130K->3130K(1056768K)], 0.0053604 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
hello world
Heap
PSYoungGen total 9216K, used 2371K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 28% used [0x00000000ff600000,0x00000000ff850c58,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 10240K, used 6768K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 66% used [0x00000000fec00000,0x00000000ff29c398,0x00000000ff600000)
Metaspace used 3170K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 344K, capacity 388K, committed 512K, reserved 1048576K
上面是GC的打印日志 分为两部分来分析他们:
GC 日志
一条Minor GC
[GC (Allocation Failure) [PSYoungGen: 8141K->776K(9216K)] 8141K->6928K(19456K), 0.0038074 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
一条Full GC
[Full GC (Ergonomics) [PSYoungGen: 776K->0K(9216K)] [ParOldGen: 6152K->6768K(10240K)] 6928K->6768K(19456K), [Metaspace: 3130K->3130K(1056768K)], 0.0053604 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
-
开头的部分有两种: GC 表示是Minor GC 、另外一种是Full GC. GC (Allocation Failure) 是内存分配失败的导致一次Minor GC,Eden 区满了。然后Full GC 是由于上面的第五条。
-
GC 收集的内存变化
[PSYoungGen: 8141K->808K(9216K)]
PSYoungGen : PS 表示Parallel Scavenge收集器 复制算法的收集器 内存大小的变化 7340K 内存发生了变化 其中部分被垃圾回收器清除了,部分复制到了To Survivor 区域
- 0.0045558 secs 垃圾回收时所用的事件
新生代
PSYoungGen total 9216K, used 7364K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
9216K / 1024 = 9m 我们设置了新生代的内存大小为10M ,Eden 跟 Survivor 空间的比值 8:1 但是有两个Surivor 所有只有一个Servivor 是可以用的。 所以就是9m
eden space 8192K
from space 1024K
to space 1024K
eden 8m from 1m to 1m 但是to的used为0%。
当新生代已经无法容纳下一个新创建的对象,新创建的对象将直接在老年代创建
public class MyGcTest02 {
public static void main(String[] args) {
int size = 1024 * 1024; //1m 大小
byte[] byteArray1 =new byte[2*size];
byte[] byteArray2 =new byte[2*size];
byte[] byteArray4 =new byte[3*size];
byte[] byteArray7 =new byte[3*size];
System.out.println("hello world");
}
}
上面的代码在非Dubug模式下并不会造成full gc
[GC (Allocation Failure) [PSYoungGen: 6093K->776K(9216K)] 6093K->4880K(19456K), 0.0026164 secs] [Times: user=0.09 sys=0.02, real=0.00 secs]
hello world
Heap
PSYoungGen total 9216K, used 7241K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 78% used [0x00000000ff600000,0x00000000ffc50598,0x00000000ffe00000)
from space 1024K, 75% used [0x00000000ffe00000,0x00000000ffec2020,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 10240K, used 4104K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 40% used [0x00000000fec00000,0x00000000ff002020,0x00000000ff600000)
Metaspace used 3170K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 344K, capacity 388K, committed 512K, reserved 1048576K
这里的应该是前两个分配完了,第三个字节数据的时候申请内存触发了Minor GC,结束新生代大小592K,前面创建的4m > To Survivor. 则直接晋身到老年代了(4104k) 有空间存放第一个3M存放后小于一半,第四个字节数组也不会触发GC ,第四个的创建也是新生代。
System.gc();
System.gc();方法会让JVM执行一次垃圾回收,它会触发以西Full Gc
System.gc();
启动参数如下:
-verbose:gc
-XX:+PrintGCDetails
Gc 日志如下:
[GC (System.gc()) [PSYoungGen: 3340K->744K(38400K)] 3340K->752K(125952K), 0.0008654 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 744K->0K(38400K)] [ParOldGen: 8K->595K(87552K)] 752K->595K(125952K), [Metaspace: 3029K->3029K(1056768K)], 0.0046903 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 38400K, used 998K [0x00000000d5d00000, 0x00000000d8780000, 0x0000000100000000)
eden space 33280K, 3% used [0x00000000d5d00000,0x00000000d5df9b20,0x00000000d7d80000)
from space 5120K, 0% used [0x00000000d7d80000,0x00000000d7d80000,0x00000000d8280000)
to space 5120K, 0% used [0x00000000d8280000,0x00000000d8280000,0x00000000d8780000)
ParOldGen total 87552K, used 595K [0x0000000081600000, 0x0000000086b80000, 0x00000000d5d00000)
object space 87552K, 0% used [0x0000000081600000,0x0000000081694f68,0x0000000086b80000)
Metaspace used 3043K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 330K, capacity 388K, committed 512K, reserved 1048576K
元空间导致的Full GC
public class MyTest4 {
public static void main(String[] args) {
for (;;){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(MyTest4.class);
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor)(var1, var2, args1, proxy) ->
proxy.invokeSuper(var1,args1)
);
try {
// Thread.sleep(50);
}catch (Exception e){
e.printStackTrace();
}
Object o = enhancer.create();
}
}
}
GC 参数: -XX:MetaspaceSize=20m 元空间大小为20m
-verbose:gc
-XX:+PrintGCDetails
-XX:MetaspaceSize=20m
Gc log
[GC (Allocation Failure) [PSYoungGen: 33280K->2349K(38400K)] 33280K->2357K(125952K), 0.0030541 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 35629K->2280K(38400K)] 35637K->2296K(125952K), 0.0031842 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 35560K->2776K(38400K)] 35576K->2800K(125952K), 0.0029843 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 36056K->3288K(71680K)] 36080K->3320K(159232K), 0.0030766 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 69848K->4792K(71680K)] 69880K->4832K(159232K), 0.0051226 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 71352K->5112K(137216K)] 71392K->5808K(224768K), 0.0061339 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 137208K->5504K(138240K)] 137904K->7800K(225792K), 0.0059711 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Metadata GC Threshold) [PSYoungGen: 128788K->5936K(269824K)] 131084K->9248K(357376K), 0.0044505 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Metadata GC Threshold) [PSYoungGen: 5936K->0K(269824K)] [ParOldGen: 3312K->8917K(62464K)] 9248K->8917K(332288K), [Metaspace: 20223K->20223K(1069056K)], 0.0474020 secs] [Times: user=0.27 sys=0.00, real=0.05 secs]
[GC (Allocation Failure) [PSYoungGen: 263168K->2752K(270848K)] 272085K->11669K(333312K), 0.0026946 secs] [Times: user=0.09 sys=0.03, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 265920K->4916K(325632K)] 274837K->13833K(388096K), 0.0034723 secs] [Times: user=0.13 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 325428K->7136K(329728K)] 334345K->16085K(392192K), 0.0054073 secs] [Times: user=0.09 sys=0.03, real=0.00 secs]
[GC (Metadata GC Threshold) [PSYoungGen: 58573K->7488K(396288K)] 67522K->16437K(458752K), 0.0060224 secs] [Times: user=0.08 sys=0.03, real=0.01 secs]
[Full GC (Metadata GC Threshold) [PSYoungGen: 7488K->0K(396288K)] [ParOldGen: 8949K->16259K(91136K)] 16437K->16259K(487424K), [Metaspace: 33916K->33916K(1081344K)], 0.0341647 secs] [Times: user=0.16 sys=0.00, real=0.03 secs]
[GC (Allocation Failure) [PSYoungGen: 388096K->2592K(397312K)] 404355K->18851K(488448K), 0.0027530 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 390688K->4672K(504832K)] 406947K->20931K(595968K), 0.0035094 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
JDK1.8 默认的垃圾回收算法
使用 -XX:+PrintCommandLineFlags 会打印JVM 的命令行启动参数
-XX:InitialHeapSize=132730432 -XX:MaxHeapSize=2123686912 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
C:\Users\86188>java -version
java version "1.8.0_212"
Java(TM) SE Runtime Environment (build 1.8.0_212-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.212-b10, mixed mode)
在JDK1.8.0.212 使用的是ParallelGC 、新生代是Parallel Scavenge 老年代是Parallel Old
JVM设置阈值 PretenureSizeThreshold
注意:
只有当使用的垃圾回收器是SerialGC的时候本参数才有效。下面我们设置PretenureSizeThreshold=4194304 (1024* 1024*4 = 4m) 当对象大于4M直接分配到老年代。
-verbose:gc
-Xmx20m
-Xms20m
-Xmn10m
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
-XX:PretenureSizeThreshold=4194304
-XX:+UseSerialGC
java代码如下我们直接生成一个5M的对象:
public class MyGcTest03 {
public static void main(String[] args) {
int size = 1024 * 1024; //1m 大小
byte[] byteArray1 =new byte[5*size];
}
}
GC 的日志如下:
Heap
def new generation total 9216K, used 2161K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 26% used [0x00000000fec00000, 0x00000000fee1c590, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
tenured generation total 10240K, used 5120K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 50% used [0x00000000ff600000, 0x00000000ffb00010, 0x00000000ffb00200, 0x0000000100000000)
Metaspace used 3113K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 338K, capacity 388K, committed 512K, reserved 1048576K
tenured generation 5120 就是5m 的大小.对象直接创建在了老年代。如果我们直接去掉-XX:UseSerialGC参数。但是保留PretenureSizeThreshold 对象还是创建在了Eden区。
Heap
PSYoungGen total 9216K, used 7281K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 88% used [0x00000000ff600000,0x00000000ffd1c5a0,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 10240K, used 0K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 0% used [0x00000000fec00000,0x00000000fec00000,0x00000000ff600000)
Metaspace used 3131K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 342K, capacity 388K, committed 512K, reserved 1048576K
如果无法在新生代分配(对象的大小大于或者等于堆大小),直接在老年代创建,且不会触发GC
public class MyGcTest05 {
public static void main(String[] args) {
int size = 1024 * 1024; //1m 大小
byte[] byteArray1 =new byte[8*size]; // 不会触发Minor GC
}
}
GC日志如下,对象直接创建在了老年代:
Heap
PSYoungGen total 9216K, used 2320K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 28% used [0x00000000ff600000,0x00000000ff844260,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
ParOldGen total 10240K, used 8192K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 80% used [0x00000000fec00000,0x00000000ff400010,0x00000000ff600000)
Metaspace used 3042K, capacity 4556K, committed 4864K, reserved 1056768K
class space used 323K, capacity 392K, committed 512K, reserved 1048576K
下面的情况则会触发一次Minor GC
byte[] byteArray2 =new byte[4*size];
byte[] byteArray1 =new byte[7*size];
如果老年代满了就会触发Full GC
我们创建两个8m 的Eden区的大小还是8m 因为无法分配导致直接两个都会分配到老年代,老年代是10m,无法容纳第二个对象就会触发Full GC ,并且会出现OOM。
byte[] byteArray2 =new byte[8 *size];
byte[] byteArray3 =new byte[8 *size];
无法分配导致的Full GC,完了还是无法分配就会抛出OutOfMemory:
[GC (Allocation Failure) [PSYoungGen: 1997K->776K(9216K)] 10189K->8976K(19456K), 0.0008958 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 776K->696K(9216K)] 8976K->8896K(19456K), 0.0008225 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 696K->0K(9216K)] [ParOldGen: 8200K->8798K(10240K)] 8896K->8798K(19456K), [Metaspace: 3167K->3167K(1056768K)], 0.0046045 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(9216K)] 8798K->8798K(19456K), 0.0002650 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(9216K)] [ParOldGen: 8798K->8780K(10240K)] 8798K->8780K(19456K), [Metaspace: 3167K->3167K(1056768K)], 0.0051171 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.sqhtech.gc.MyGcTest06.main(MyGcTest06.java:7)
Heap
PSYoungGen total 9216K, used 410K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 5% used [0x00000000ff600000,0x00000000ff666800,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 10240K, used 8780K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 85% used [0x00000000fec00000,0x00000000ff493140,0x00000000ff600000)
Metaspace used 3232K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 351K, capacity 388K, committed 512K, reserved 1048576K
动态的年龄调整以及 MaxTenuringThreshold 最大阈值的设置
启动参数
/**
- 经历了多次GC后存活的对象可以在From Survivor To Servivor之间来回复制
- 每一复制就是1岁
- -XX:MaxTenuringThreshold=4 设置晋升到老年代对象的最大的存活年龄。
- 这个阈值是可以动态调整的,设置值为最大值
- JVM可以在年龄等于2的时候就吧新生代的对象晋升到老年代,但是最大值不会超过
- 设置的值
- ParallGC 15 CMS 默认为6 G1 默认为15
- 如果发现到了某个年龄后总大小以及大于Survivor空间的50%那么这时候就需要自动的调整
- 阈值,不能再继续等到默认的15次GC后才能完成晋升。会导致空间大小不足,导致对象直接从
- Eden区晋升到老年代
- -XX:+PrintTenuringDistribution 打印GC日志
*/
-verbose:gc
-Xmx20m
-Xms20m
-Xmn10m
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=5
-XX:+PrintTenuringDistribution
java 不停的创建对象,触发GC
public static void main(String[] args) {
int size = 1024 * 1024 ;
for(int i= 0;i<1000;i++){
try {
Thread.sleep(50);
byte[] buffer = new byte[ 1 *size];
}catch (Exception e){
e.printStackTrace();
}
}
}
GC log
Desired survivor size 1048576 bytes, new threshold 5 (max 5)
[PSYoungGen: 8097K->776K(9216K)] 8105K->784K(19456K), 0.0008843 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure)
Desired survivor size 1048576 bytes, new threshold 4 (max 5)
[PSYoungGen: 8099K->824K(9216K)] 8107K->832K(19456K), 0.0008337 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure)
Desired survivor size 1048576 bytes, new threshold 3 (max 5)
[PSYoungGen: 8149K->0K(9216K)] 8157K->688K(19456K), 0.0009346 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure)
Desired survivor size 1048576 bytes, new threshold 2 (max 5)
[PSYoungGen: 7326K->0K(9216K)] 8014K->688K(19456K), 0.0003190 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure)
Desired survivor size 1048576 bytes, new threshold 1 (max 5)
[PSYoungGen: 7327K->0K(9216K)] 8015K->688K(19456K), 0.0004307 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Desired survivor size 1048576 bytes, new threshold 1 (max 5)
new threshold num 这里的数值就是实际晋升到老年代的年龄的大小 ,这里的max如果内存分配不频繁的时候GC 还将可能变为最大值。
美团技术博客 : 从实际案例聊聊Java应用的GC优化