JVM07:常见的几种垃圾回收器和引用类型
深入理解垃圾回收器
GC算法如引用计数算法、复制算法、标记清除算法、标记整理算法都是方法论,垃圾回收器就是这些算法对应的落地的实现。
四种垃圾回收器
1、串行垃圾回收器,单线程工作,执行GC时会停止所有的线程直到GC结束(STW:Stop the World)。其原理如下图所示。
2、并行垃圾回收器,多线程工作,也会导致STW。其原理如下图所示。
3、并发垃圾回收器,在回收垃圾的同时,可以正常执行线程,并行处理,但是如果是单核CPU,只能交替执行。其原理如下图所示。
4、G1垃圾回收器,将堆内存分割成不同的区域,然后并发的对其进行垃圾回收。Java9以后为默认的垃圾回收器。其原理如下图所示。
查看默认的垃圾回收器:java -XX:+PrintCommandLineFlags -version
Java的垃圾回收器有哪些?
Java曾经由7种垃圾回收器,现在有6种。主要垃圾回收器的位置分布和关系如下图所示。上图中,红色箭头表示新生区中使用了对应的垃圾回收器,在老年区只能使用对应箭头指向的垃圾回收器。蓝色箭头表示曾经的垃圾回收器有过的对应关系。
6种垃圾回收器名称分别是:
DefNew :默认的新一代 【Serial 串行】
Tenured :老年代 【Serial Old】
ParNew :并行新一代 【并行ParNew】
PSYoungGen :并行清除年轻代 【Parallel Scavcegn】
ParOldGen:并行老年区
JVM的Server/Client模式
现在的JVM默认都是Server模式,Client几乎不会使用。以前32位的Windows操作系统,默认都是Client的 JVM 模式,64位的默认都是 Server模式。
垃圾回收器之间的组合关系
上述6种垃圾回收器都是组合使用的,新生区使用了某种垃圾回收器,养老区会使用与之对应的垃圾回收器,并不是自由搭配的。如下图所示。
如何选择垃圾回收器
1、单核CPU,单机程序,内存小。选择-XX:UseSerialGC
。
2、多核CPU,吞吐量大,后台计算。选择XX:+UseParallelGC
。
3、多核CPU,不希望有时间停顿,能够快速响应。选择-XX:+UseParNewGC
或者 XX:+UseParallelGC
。
G1垃圾回收器
以往垃圾回收器的特点
1、年轻代和老年代是各自独立的内存区域。
2、年轻代使用Eden+s0+s1复制算法。
3、老年代垃圾收集必须扫描整个老年代的区域。
4、垃圾回收器原则:尽可能少而快的执行GC。
G1垃圾回收器的原理
G1(Garbage-First)垃圾回收器 ,是面向服务器端的应用的回收器。其原理如下图所示。原理:将堆中的内存区域打散,默认分成2048块。不同的区间可以并行处理垃圾,在GC过程中,幸存的对象会复制到另一个空闲分区中,由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩)。
使用G1垃圾回收器:-XX:+UseG1GC
G1垃圾回收器最大的亮点是可以自定义垃圾回收的时间。
设置最大的GC停顿时间(单位:毫秒):XX:MaxGCPauseMillis=100
,JVM会尽可能的保证停顿小于这个时间。
G1垃圾回收器的优点
没有内存碎片。
可以精准的控制垃圾回收时间。
强引用、软引用,弱引用和虚引用
主要学习三个引用类:SoftReference
、WeakReference
和PhantomReference
强引用
假设出现了异常或OOM,只要是强引用的对象,都不会被回收。强引用就是导致内存泄露的原因之一。
package com.wunian.ref;
/**
* 强引用
* -XX:+PrintGCDetails -Xms5m -Xmx5m
*/
public class StrongRefDemo {
public static void main(String[] args) {
Object o1=new Object();//这样定义的默认就是强引用
Object o2=o1;
o1=null;
System.gc();
System.out.println(o1);//null
System.out.println(o2);//java.lang.Object@6e0be858
}
}
软引用
相对于强引用弱化了。如果系统内存充足,GC不会回收该对象,但是内存不足的情况下就会回收该对象。
package com.wunian.ref;
import java.lang.ref.SoftReference;
/**
* 软引用
* -XX:+PrintGCDetails -Xms5m -Xmx5m
*/
public class SoftRefDemo {
public static void main(String[] args) {
Object o1=new Object();//这样定义的默认就是强引用
//Object o2=o1;
SoftReference<Object> o2=new SoftReference<>(o1);//软引用
System.out.println(o1);//java.lang.Object@6e0be858
System.out.println(o2.get());//得到引用的值 java.lang.Object@6e0be858
o1=null;
try {
byte[] bytes=new byte[10*1024*1024];
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(o1);//null
System.out.println(o2.get());//null //由于堆内存不足被回收
}
//System.gc();
}
}
弱引用
不论内存是否充足,只要是GC就会回收该对象。
package com.wunian.ref;
import java.lang.ref.WeakReference;
/**
* 弱引用
* -XX:+PrintGCDetails -Xms5m -Xmx5m
*/
public class WeakRefDemo {
public static void main(String[] args) {
Object o1=new Object();//这样定义的默认就是强引用
WeakReference<Object> o2 = new WeakReference<>(o1);
System.out.println(o1);//java.lang.Object@6e0be858
System.out.println(o2.get());//得到引用的值 java.lang.Object@6e0be858
o1=null;
System.gc();
System.out.println(o1);//null
System.out.println(o2.get());//null
}
}
软引用、弱引用的使用场景
假设现在有一个应用,需要读取大量的本地图片。
1、如果每次读取图片都要从硬盘中读取,影响性能。
2、一次加载到内存中,可能造成内存溢出。
我们的思路:
1、使用一个HashMap保存图片的路径和内容。
2、内存足够,不清理。
3、内存不足,清理加载到内存中的数据。
虚引用
虚就是虚无,虚引用就是没有这个引用。虚引用需要结合队列使用,其主要作用是跟踪对象的垃圾回收状态。
package com.wunian.ref;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.TimeUnit;
/**
* 虚引用
*/
public class PhantomRefDemo {
public static void main(String[] args) throws InterruptedException {
Object o1=new Object();
//虚引用需要结合队列使用
ReferenceQueue<Object> referenceQueue=new ReferenceQueue<>();
PhantomReference<Object> objectPhantomReference=new PhantomReference<>(o1,referenceQueue);
System.out.println(o1);//java.lang.Object@6e0be858
System.out.println(objectPhantomReference.get());//null
System.out.println(referenceQueue.poll());//null
o1=null;
System.gc();
TimeUnit.SECONDS.sleep(1);
System.out.println(o1);//null
System.out.println(objectPhantomReference.get());//null
//这好比是一个垃圾桶,通过队列来检测哪些对象被清理了,可以处理一些善后工作
System.out.println(referenceQueue.poll());//java.lang.ref.PhantomReference@61bbe9ba
}
}