在java.lang.ref包中提供了引用对象类,通过这些引用对象类,可以支持在某种程度上与垃圾回收器(gc)的交互,这个包中的类有:Reference,ReferenceQueue,WeakReference,SoftReference,PhantomReference,对应不同的引用对象类,Java中有以下四种对象引用。
1、强引用(Strong Reference)
强引用就是我们平常使用的对象引用,比如我们新建了一个String对象,String str=new String(“test”),这个str变量中就存储了对String对象的一个强引用。
强引用强在哪里?实际上这个强指的是与垃圾回收器的交互方式,如果一个对象是通过强引用连接(强引用可达),那么当这个对象正在使用的时候,垃圾回收器就不能销毁这个对象,即使当内存空间不足的时候,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会回收具有强引用的对象来解决内存不足的问题。
2、弱引用(Weak Reference)
通过WeakReference建立的引用为弱引用,与强引用不同的是弱引用对象并不禁止垃圾回收器回收它,垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间是否足够使用,都会回收它的内存,由于垃圾回收器线程优先级很低,所以不一定很快就发现只具有弱引用的对象,而且垃圾回收器有时候可能需要多次扫描才会发现一个弱引用对象。
弱引用可以与一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,jvm就会把弱引用加入到与之关联的引用队列中。
弱引用常常用来实现规范化的映射结构如:WeakHashMap
示例:
public class test {
public static void main(String[] args) throws InterruptedException{
WeakReference<String> weakRef=new WeakReference<String>(new String("123"));
System.out.println(weakRef.get());
System.gc();
Thread.sleep(2000);
System.out.println(weakRef.get());
}
}
输出结果:
123
null
在调用gc以后,垃圾回收器回收弱引用所指向的对象,当我们再获取该对象时便会返回null值。
3、软引用(Soft Reference)
软引用对象会在JVM响应内存需要时,由垃圾回收器决定是否清除此对象(内存是否充足),这决定于垃圾回收器对这些对象正在消耗的内存有多么急切。
软可达对象的所有软引用都要保证在虚拟机抛出OutOfMemoryError之前已经被清除,虚拟机实现不鼓励清除最近访问或使用过的软引用,与弱引用对象相比软引用对象将在内存中保存尽可能长的时间。
弱引用可以与一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,jvm就会把软引用加入到与之关联的引用队列中。
软引用对象可以用于实现内存敏感的缓存。
示例:
package test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
class Student{
private String id; //学号
private int score; //成绩
private byte[] bytes;
public Student(String id,int score){
this.id=id;
this.score=score;
this.bytes=new byte[1024*1024*200]; //200M
}
public void setId(String id){
this.id=id;
}
public String getId(){
return this.id;
}
public void setScore(int score){
this.score=score;
}
public int getScore(){
return this.score;
}
}
class StudentBuffer{
private int mode;
private Map<String,Object> buffer;
public StudentBuffer(int mode){
this.mode=mode;
buffer=new HashMap<String,Object>();
}
public void put(Student student){
switch(mode){
case 0:
buffer.put(student.getId(),new SoftReference<Student>(student));
break;
case 1:
buffer.put(student.getId(),student);
break;
}
}
public Student get(String name){
switch(mode){
case 0:
return ((SoftReference<Student>)buffer.get(name)).get();
case 1:
return (Student)buffer.get(name);
default:
return null;
}
}
public int size(){
return buffer.size();
}
}
public class test {
public static void main(String[] args) throws InterruptedException, IOException{
System.out.println("***************");
System.out.println("0:使用软引用,1:启用强引用");
System.out.println("***************");
System.out.println("请输入启用的缓存模式:");
BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
String mode=reader.readLine();
StudentBuffer buffer=new StudentBuffer(Integer.parseInt(mode));
for(int i=0;i<100;i++){
buffer.put(new Student(i+"",i));
System.out.println("已经加入:"+i);
}
System.out.println(buffer.size());
}
}
软引用:
***************
0:使用软引用,1:启用强引用
***************
请输入启用的缓存模式:
0
已经加入:0
……
已经加入:99
100
强引用:
***************
0:使用软引用,1:启用强引用
***************
请输入启用的缓存模式:
1
已经加入:0
…
已经加入:9
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at test.Student.<init>(tt.java:18)
at test.tt.main(tt.java:85)
在这个例子中我们采用了强引用与软引用两种方式作为缓存,每个Student实例我们申请了200M的堆内存,我们可以看到采用强引用的时候,循环到第11次的时候,JVM堆内存被消耗完毕,会报OutOfMemoryError错误,而当我们采用软引用的时候,可以顺利的进行put,不会报错,因为当我们的内存不足时,垃圾回收器会在抛出OutOfMemoryError错误之前回收软引用指向的对象,这样我们又会有足够的堆内存,所以我们可以一直的缓存进去(原来的缓存所保存的对象空间已经被GC释放掉),而不用关注内存是否足够,在我本机上执行的时候明显能够感觉到中间有几次明显的停顿,这几个停顿的时间应该就是GC在回收整理释放内存。
这里面有一个需要注意的地方,我们看到采用软引用的时候,最后打印出的大小为100,缓存真的大小为100?,当然不是,我的本机内存明显不可能有这么大的堆内存,StudentBuffer类中采用的是HashMap存储数据,GC仅仅是将HashMap中保存的软引用指向的对象空间进行释放,而HashMap中对应的键值对并没有被删除,所以最后的大小仍然存在,那我们如何得到真正的大小,这时我们就可以在StudentBuffer类中定义一个ReferenceQuene对象,然后每次构造软引用的时候将该队列对象传入,这样每次GC就会将要删除的软引用放入到这个队列中,我们根据这个队列进行相应的HashMap删除操作,具体实现方式与WeakHashMap差不多,可以改写成如下形式
package test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
class StudentReference extends SoftReference<Student>{
private String id;
public StudentReference(Student referent) {
super(referent);
this.id=referent.getId();
}
public StudentReference(Student referent, ReferenceQueue<Student> q) {
super(referent, q);
this.id=referent.getId();
}
public void setId(String id){
this.id=id;
}
public String getId(){
return this.id;
}
}
class Student{
private String id; //学号
private int score; //成绩
private byte[] bytes;
public Student(String id,int score){
this.id=id;
this.score=score;
this.bytes=new byte[1024*1024*200]; //200M
}
public void setId(String id){
this.id=id;
}
public String getId(){
return this.id;
}
public void setScore(int score){
this.score=score;
}
public int getScore(){
return this.score;
}
}
class StudentBuffer{
private int mode;
private Map<String,Object> buffer;
private ReferenceQueue<Student> queue = new ReferenceQueue<Student>();
public StudentBuffer(int mode){
this.mode=mode;
buffer=new HashMap<String,Object>();
}
public void put(Student student){
switch(mode){
case 0:
buffer.put(student.getId(),new StudentReference(student,queue));
break;
case 1:
buffer.put(student.getId(),student);
break;
}
}
public Student get(String name){
switch(mode){
case 0:
return ((StudentReference)buffer.get(name)).get();
case 1:
return (Student)buffer.get(name);
default:
return null;
}
}
public int size(){
expungeStaleEntries();
return buffer.size();
}
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
StudentReference studentRef =(StudentReference)x;
buffer.remove(studentRef.getId());
}
}
}
public class tt {
public static void main(String[] args) throws InterruptedException, IOException{
System.out.println("***************");
System.out.println("0:使用软引用,1:启用强引用");
System.out.println("***************");
System.out.println("请输入启用的缓存模式:");
BufferedReader reader=new BufferedReader(new InputStreamReader(System.in));
String mode=reader.readLine();
StudentBuffer buffer=new StudentBuffer(Integer.parseInt(mode));
for(int i=0;i<100;i++){
buffer.put(new Student(i+"",i));
System.out.println("已经加入:"+i);
}
System.out.println(buffer.size());
}
}
我们在执行这个新的代码,输入0,最后输出的值将不再是100,而是buffer的确切大小。
4、虚引用(Phantom Reference)
与上面三种引用不同,虚引用并不会决定对象的生命周期,当垃圾回收器确定在某一特定时间点上虚引用指示对象是虚可达对象,那么在那时或者以后的某一时间,它会将该引用加入ReferenceQuene队列,但是这个加入队列的方式与软引用和弱引用不同,虚引用在加入队列时并没有通过垃圾回收器自动清除,通过虚引用可到达的对象将仍然保持原状,直到所有这类引用都被清除,或者它们都变得不可到达,而为了确保可回收对象保持原状,虚引用的指示对象不能被获取,虚引用的get方法总是返回null。
虚引用主要用来跟踪对象被垃圾回收器回收的活动,程序可以通过判读引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收,如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
5、可达性
如果某一线程可以不必遍历所有引用对象而直接到达一个对象,则该对象是强可到达对象。新创建的对象对于创建它的线程而言是强可到达对象。
如果一个对象不是强可到达对象,但通过遍历某一软引用可以到达它,则该对象是软可到达对象。
如果一个对象既不是强可到达对象,也不是软可到达对象,但通过遍历弱引用可以到达它,则该对象是弱可到达对象。当清除对某一弱可到达对象的弱引用时,便可以终止此对象了。
如果一个对象既不是强可到达对象,也不是软可到达对象或弱可到达对象,它已经终止,并且某个虚引用在引用它,则该对象是虚可到达对象。
最后,当不能以上述任何方法到达某一对象时,该对象是不可到达对象,因此可以回收此对象。示例:
String str=new String("test"); //1
WeakReference<String> weakRef=new WeakReference<String>(str); //2
str=null; //3
str=weakRef.get(); //4
str=null; //5
weakRef.clear();//6
针对new String(“test”)创建的这个对象,各个步骤以后其可达性变化情况为
1:强可到达对象
2:强可到达对象
3:弱可到达对象
4:强可到达对象
5:弱可到达对象
6:不可达对象
参考: