一、 GC回收日志打印基本设置
启动设置:
-verbose:gc //开启gc日志
-XX:+PrintGCDetails //打印gc详情
-XX:+PrintGCDateStamps //打印gc时间戳
-XX:+PrintHeapAtGC //在进行GC的前后打印出堆的信息
-Xloggc:gc.log //日志输出名称
-Xms3M //初始内存
-Xmx4M //最大可用内存
日志各个字段含义简述:
2019-03-28 日期
10:02:40.628+0800: 执行回收时间
0.080: 启动JVM到该次垃圾回收时间
GC/Full GC :垃圾回收类型
Allocation Failure : 垃圾回收的原因,此时为Eden区空间不足
ParNew/CMS/Metaspace : 垃圾回收区域 新生代/老年代/元空间
[PSYoungGen: 510K->504K(1024K)] 清理前占用 -> 清理后占用(新生代内存总大小)
PS代表新生代的是使用的是Parallel Scavenge收集器垃圾收集器
[ParOldGen] Par代表老年代使用的是Parallel old垃圾回收器
0.0006474 secs 表示回收的时间
510K->536K(3584K) 整个堆内存清理前占用 ——> 清理后占用(对内存总大小)
[Metaspace: 3304K->3304K(1056768K)] 这部分表示的是方法区的内存大小变化
[Times: user=0.00 sys=0.00, real=0.00 secs]
user 代表的是用户状态耗时0.00,
sys 系统状态耗时0.00,
real CPU实际的执行时间为0.00
Ergonomics:HotSpot自动选择和调优引发的FullGC
二、了解其概念及其区别
我们在实际开发中,往往会执行一些方法加载一些参数值到内存中,后期随着业务的发展、方法的执行等等大量的数据存在内存中,结果导致内存占用过多而产生的溢出异常,因此我们期望自己的放到内存中的对象也具有生命周期(如:内存不足时,jvm会自动回收掉某些对象从而避免OOM的错误)这个时候就需要用到软引用和弱引用
从JDK 1.2之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱。
强引用:
Java中默认声明的就是强引用,如:Object object=new Object();只要强引用还存在,JVM必定不会回收掉被引用的对象,即使在内存不足的情况下,JVM宁愿抛出OOM异常,也不会回收这种对象;
如下代码:
public class StronglyReference {
public static void main(String[] args) {
Object object=new Object();
System.gc();
System.out.println("GC回收后:"+object);
try {
byte[] buff2 = new byte[1024 * 1024 * 4];
} catch (OutOfMemoryError e) {
//为了程序继续执行下去,捕获异常,如果不捕获会抛出异常且回收
System.out.println("创建对象发生OOM异常,哥,你宁愿发生异常也不回收掉哎,何必呢!");
}
//将引用设置为null,jvm在适当的时候会回收该对象
object=null;
System.out.println("设置为空后:"+object);
}
}
结果产出:
GC回收后:java.lang.Object@12a3a380
创建对象发生OOM异常,哥,你宁愿发生异常也不回收掉哎,何必呢!
设置为空后:null
经过上面的demo实例,然后分析GC日志,我们得出下面的问题和答案;
问:那么在使用强引用的时候,JVM是么时候会触发回收?
答:
- 线程结束时回收;
- 当 softReference 设置为空的时候,会被回收;
软引用(SoftReference):
官方解释:软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。在 JDK1.2 之后,用java.lang.ref.SoftReference
类来表示软引用。
查看一下事例:
public class SoftReferences {
public static void main(String[] args) {
List list=new ArrayList();
for (int i=0;i<10;i++){
byte[] softBuff = new byte[1024 * 1024 * 1];
SoftReference sr = new SoftReference(softBuff);
list.add(sr);
}
for (SoftReference sr:list) {
System.out.println(sr.get());
}
}
}
返回结果:
null
null
null
null
null
null
null
null
null
[B@12a3a380
在GC日志中我们可以看到一个关键字出现:Ergonomics
(解释上面有说明)
由此我们可以得出一个结论与解释一致
(还有一种情况,当软引用设置为空时也会被回收)
事例代码:
public static void main(String[] args) {
byte[] softBuff1 = new byte[1024 * 1024 * 2];
SoftReference sr1 = new SoftReference(softBuff1);
sr1=null;
System.out.println(sr1.get());
}
result:
Exception in thread "main" java.lang.NullPointerException
由此可见,设置为空时,对象也被回收了;
弱引用(WeakReference):
这个其实挺简单的:只要JVM进行垃圾回收,无论内存是否充足,都会回收弱引用关联的对象,在 java 中,用 java.lang.ref.WeakReference
类来表示
事例:
public class WeakReferences {
public static void main(String[] args) {
try {
WeakReference w1=new WeakReference(new Object());
System.out.println(w1.get());
System.gc();
System.out.println(w1.get());
} catch (NullPointerException e) {
System.out.println("W1 会抛出空指针异常,表示被回收");
}
}
}
result:
java.lang.Object@12a3a380
null
虚引用(PhantomReference):
官方解释:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用PhantomReference 类来表示;我们看下源码
源码:
public class PhantomReference extends Reference {
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue super T> q) {
super(referent, q);
}
}
通过源码我们可以清晰的了解到,它只有一个构造函数和一个 get()
方法,而且 get()
方法是固定的返回的null,这样玩们就知道了,我们永远无法单独的通过虚引用来获取对象,虚引用也必须要和ReferenceQueue
引用队列一起使用。这是是它和软引用、弱引用最直观的局别;
引用队列(ReferenceQueue)
官方解释:引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。
与软引用、弱引用不同,虚引用必须和引用队列一起使用(上面有提到过)。
public class PhantomReferences {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue queue=new ReferenceQueue();
Object phantomObject=new Object();
PhantomReference phantomReference=new PhantomReference (phantomObject,queue);
System.out.println(phantomReference.get());
phantomObject=null;
System.gc();
System.out.println(phantomReference.get());
Thread.sleep(200);
System.out.println(queue.poll());
}
}
result:
null
null
java.lang.ref.PhantomReference@12a3a380
问:为什么会有对象输出呢?
答:我们已经创建引用队列,并且在引用队列里面创建了对象phantomObject,当GC回收的时候发现虚引用,会将这个对象丢到队列里面,而现在插入队列的时候发现已经有了这个对象,会失败,然后会回收掉这个对象并且返回,所以会有返回对象