既然创建的对象,在不使用后不会立马清除,会在GC时才真正清除,那么,
1.什么时候触发GC?
2.那一部分触发GC?
3.如何进行回收?
首先,我们从对象存储的部分看起,即堆内存。
我们代码在JVM上一直不停的跑,此时就会在堆内存中创建对象,此处假设我们初始的参数配置如下:
-Xms20M -Xmx20M -Xmn10M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256 -XX:SurvivorRatio=8
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/logs
配置详情,请浏览最下方。
然后运行一下测试方法:
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(30000);
byte[] b=new byte[512*1024];
for(int i=0;i<50;i++){
Thread.sleep(1000);
b=new byte[512*1024];
}
}
}
这里,每次new一个0.5M的对象。
先想象一下,当这段代码运行的时候,内存是如何变化的?
由上面配置,我们知道,新生代的内存大小是10M,默认Eden区:from:to的比例是:8:1:1
那么eden区占据了8M的内存,from和to各1M内存空间,启动程序,使用jps查看运行的进程PID。
C:\Users\HBL>jps
8080
12036 RemoteMavenServer
14196 Launcher
16996 Test
9268 Jps
找到我们的Test的进程,然后执行:
jstat -gc 16996 1000 1000
此方法是每隔一秒钟,更新出一条jstat统计语句,共打印1000次,当然程序结束也就不打印了。
这就是运行时实时查看内存变化。
这里截取了第二次和第三次的gc,由于项目启动会有一些配置对象,所以从第二次开始看
1.当上图第一个GC发生的时候,此时Eden区由8M左右的大小,变为0.5M,而s0由s1中的1M变为0.5M,此时可能会发现老年代增加了大约0.5M的数据,这其实是配置对象,所以开始就省略了一次GC截图。
2.当第二次发生GC后,Eden区又从8M左右变为0.5M,而s1由s0的0.5M变为0.5M,此时老年代没有增加数据。
注:
这里肯定存在一个疑问?
不是JVM默认15代后才进入老年代码,那这里为什么实际运行第二次GC就进入了?
这里要注意,当>n代的对象内存大于s1或s0的内存一半的时候,就会将大于n的代的对象直接晋升老年代。
接着你会问,那么为什么第一次都快1M了都没进入老年?
因为第一次GC的时候,即使大于一半也不会直接进入老年代,此处n必须大于1即上面的晋升条件,只是符合n>1的代数才会晋升。
接着我们现在清楚看到了内存在GC后的变化,用图展示如下:
要注意:
1.ParNew垃圾回收器使用的复制算法,同时它是多线程的。
2.ParNew垃圾回收器在执行过程中会造成STW,即它运行时,代码是不能运行的。
那么老年代如何进行垃圾回收,那就是ConcMarkSweep垃圾回收器,简称CMS垃圾回收器。
CMS的工作分四个步骤:
1)初始标记
此阶段,标记出GC Root直接引用的对象,会造成STW,即停止程序运行。
2)并发标记
这个阶段系统可以创建各种对象,继续运行。对老年代所有对象进行GC Root追踪。这个是最耗时的
3)重新标记
由于第二阶段是并行运行,所以会有很多对象创建和对象变为垃圾对象,所以此阶段需要重新标记,它是STW的。
4)并发清理
此阶段跟程序并行执行清理工作。
注:
并发标记和并发清理阶段会有Concurrent Mode Failture错误发生,它是老年代在进行CMS回收时,创建的新对象内存放不下从而报的错误。默认92%进行CMS。
此时,CMS就会停止,转为serial old垃圾回收。
最后,看一下JVM配置:
jps:查看Java进程的PID,
jstat -gc PID:查看进程的虚拟机。
jstat -gc PID 1000 10:每隔一秒,更新出一条jstat统计语句,更新十次。
-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5
每5次full gc触发一次compaction,也就是压缩
jmap -dump:live,format=b,file=文件名 [服务进程ID]:生成内存快照
-XX:+HeapDumpOnOutOfMemoryError:当内存发生oom自动dump一份内存快照出来
-XX:HeapDumpPath=内存快照存放的地址