一.概述:
垃圾回收的方法是判断没有个对象的可触及性,即从根节点开始是否可以访问到这个对象。如果访问到了,说明当前对象正在使用,如果从所以根节点都无法访问到某个对象,说明这个对象不再使用了。一般情况下,此对象要被回收。但是,一个无法触及的对象有可能在某一个条件下复活自己,那么对它回收就不合理了。因此,需要给出一个对象可触及性状态的定义,并规定在什么状态下,才可以安全回收对象。
可触及性包括三种:
1.可触及的:从根节点可以触及到这个对象。
2.可复活的:对象所有引用被释放,但是在finalize()中可能复活该对象。
3.不可触及的:对象的finalize()被调用后,并且没有被复活,那么就进入不可触及状态,不可触及的对象不可能被复活,因为finalize()函数只会被调用一次。
只有不可触及的对象可以回收。
如何确定根节点:
- 栈中引用的对象:线程栈中函数引用的局部变量。
- 方法区中静态成员或者常量引用的对象(全局对象)
- JNI方法栈中引用对象
二.对象的复活:
当对象没有被引用的时候,这时如果gc,如何不被gc回收呢?如下代码:
/**
* 对象的复活:
* 1.System.gc()之前会执行对象的finalize()方法。
* 2.对象的finalize()方法让obj又复活了。
* 3.但是一个对象的finalize()方法只能执行一次,所以第二次不能复活。
* 4.System.gc()只是开始回收,调用了finalize()后才是真正意义的回收。
*
* 如果没有"obj=null;//**第二次,不可复活 "这个语句,obj就不可能被回收了呢?不见得!!!很危险**
* 经验:
* 1.避免使用finalize().
* 2.优先级低,何时调用不确定。(什么时候gc,程序无法确定,系统决定)
* 3.可以用try-catch-finally来替代它。
* Created by chenyang on 2017/2/2.
*
*/
public class CanReliveObj {
public static CanReliveObj obj;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("CanReliveObj finalize called");
obj=this;
}
@Override
public String toString() {
return "I am CanReliveObj";
}
public static void main(String[] args) throws InterruptedException{
obj=new CanReliveObj();
obj=null;//引用清空,可复活
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj 是 null");
}else {
System.out.println("obj可用");
}
System.out.println("第2次gc");
obj=null;//不可复活
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj是null");
}else {
System.out.println("obj可用");
}
}
}
如上面的代码,finalize()的危险,因为如果没有"obj=null;第二次,不可复活 "这个语句,obj就不可能被回收了呢?不见得!!!很危险
finalize()浅析:
在说明finalize()的用法之前要树立有关于java垃圾回收器几个观点:
- "对象可以不被垃圾回收" : java的垃圾回收遵循一个特点, 就是能不回收就不会回收.只要程序的内存没有达到即将用完的地步, 对象占用的空间就不会被释放.因为如果程序正常结束了,而且垃圾回收器没有释放申请的内存, 那么随着程序的正常退出, 申请的内存会自动交还给操作系统; 而且垃圾回收本身就需要付出代价, 是有一定开销的, 如果不使用,就不会存在这一部分的开销.
- 垃圾回收只能回收内存, 而且只能回收内存中由java创建对象方式(堆)创建的对象所占用的那一部分内存, 无法回收其他资源, 比如文件操作的句柄, 数据库的连接等等.
- 垃圾回收不是C++中的析构. 两者不是对应关系, 因为第一点就指出了垃圾回收的发生是不确定的, 而C++中析构函数是由程序员控制(delete) 或者离开器作用域时自动调用发生, 是在确定的时间对对象进行销毁并释放其所占用的内存.
- 调用垃圾回收器(GC)不一定保证垃圾回收器的运行
- finalize()的功能 : 一旦垃圾回收器准备释放对象所占的内存空间, 如果对象覆盖了finalize()并且函数体内不能是空的, 就会首先调用对象的finalize(), 然后在下一次垃圾回收动作发生的时候真正收回对象所占的空间.
- finalize()有一个特点就是: JVM始终只调用一次. 无论这个对象被垃圾回收器标记为什么状态, finalize()始终只调用一次. 但是程序员在代码中主动调用的不记录在这之内.
经验:
- 避免使用finalize(),操作不慎可能导致错误。
- 优先级低,何时被调用, 不确定。何时发生GC不确定
- 可以使用try-catch-finally来替代它。
三.引用的类型和可触及性的强度:
java提供了4个级别的引用:强引用,软引用,弱引用和虚引用。除了强引用外,其他3种引用均可以在java.lang.ref包中找到。如下显示了三种引用对应的类。
强引用--指向的对象不会被回收
强引用就是程序中一般使用的引用类型,强引用的对象时可触及的,不会被回收。但是,软引用,弱引用和虚引用的对象是软可触及,弱可触及和虚可触及,在一定的条件下,是可以被回收的。
下面是个强引用例子:
Class A{
StringBuffer str=new StringBuffer("Hello world");
public void getStr(){
StringBuffer str1=str;
StringBuffer str2=str1;
}
}
假设str是对象成员变量,那么局部变量str1将被分配在栈上,而对象StringBuffer实例被分配在了堆上。局部变量str1指向StringBuffer实例所在堆空间,通过str1可以操作该实例,那么str1就是StringBuilder实例的强引用。
强引用特点:
- 强引用可以直接访问目标对象。
- 强引用所指向的对象在任何时候都不会被系统回收,宁可抛出OOM异常,也不会回收强引用所指的对象。
- 强引用可导致内存泄漏。
软引用--指向的对象可以被回收的引用
如果一个对象只有被软引用所引用,当堆空间不足的时候回被回收。下面的例子演示了软引用会在系统堆内存不足的时候被回收:
import java.lang.ref.SoftReference;
/**软引用:
* 软引用是比强引用弱一点的引用类型,如果一个对象仅仅只有软引用,那么当堆空间不足时,
* 就会被回收。
*
* 启动参数:-Xmx10m -Xms10m -XX:+UseSerialGC -XX:+PrintGCDetails -Xmn1m
* Created by chenyang on 2017/2/2.
*/
public class SoftRef {
public static class User{
public User(int id,String name){
this.id=id;
this.name=name;
}
public int id;
public String name;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
User u=new User(1,"geym");//强引用
SoftReference userSoftRef=new SoftReference(u);//建立软引用
u=null;//去除强引用
System.out.println(userSoftRef.get());//软引用存在
System.gc();
System.out.println("After GC:");
System.out.println(userSoftRef.get());//gc后软引用还存在,因为堆空间还是比较充足的。
byte[] b=new byte[1024*924*7];//堆空间被其他对象占据,这是堆空间比较紧张
System.gc();
System.out.println(userSoftRef.get());//堆空间比较紧张时,软引用的对象被回收。
}
}
使用参数-Xmx10m运行上述代码,得到:
[id=1,name=geym](从软引用获取数据)
After GC:
[id=1,name=geym](GC没有清除软引用)
null (由于内存紧张,软引用虽然还在但是对象还是被回收了)
当内存资源紧张时,软引用指向的对象会被回收。所以,软引用不会引起OOM。
每一个软引用都可以附带一个引用队列,当对象的可达性状态发生改变时(由可达变为不可达),软引用对象就会进入引用队列,通过这个引用队列,就可以跟踪对象的回收情况:
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
/**
*
* 每一个软引用都可以附带一个引用队列,当对象的可达性发生改变(由可达变为不可达),
* 软引用对象就会进入引用队列。通过这个引用队列,可以跟踪对象的回收情况。
*
* 执行参数-Xmx10m
* Created by chenyang on 2017/2/2.
*/
public class SoftRefQ {
public static class User{
public User(int id,String name){
this.id=id;
this.name=name;
}
public int id;
public String name;
}
static ReferenceQueue softQueue=null;
public static class CheckRefQueue extends Thread{
@Override
public void run() {
while (true){
if(softQueue!=null){
UserSoftReference obj=null;
try {
obj=(UserSoftReference)softQueue.remove();
}catch (InterruptedException e){
e.fillInStackTrace();
}
if(obj!=null){
System.out.println("user id"+obj.uid+" is delete");
}
}
}
}
}
public static class UserSoftReference extends SoftReference{
int uid;
public UserSoftReference(User referent,ReferenceQueue super User> q){
super(referent,q);
uid=referent.id;
}
}
public static void main(String[] args) throws InterruptedException{
Thread t=new CheckRefQueue();
t.setDaemon(true);
t.start();
User u=new User(1,"chenyang");
softQueue=new ReferenceQueue();
UserSoftReference userSoftRef=new UserSoftReference(u,softQueue);
u=null;
System.out.println(userSoftRef.get());
System.gc();
System.out.println("After GC:");
System.out.println(userSoftRef.get());
System.out.println("try to create byte array and GC");
byte[] b=new byte[1024*925*7];
System.gc();
System.out.println(userSoftRef.get());
Thread.sleep(1000);
}
}
在创建一个软引用时,指定了一个软引用队列,当给定的对象实例被回收时,就会加入这个引用队列,通过队列可以跟踪对象的回收情况:
使用参数-Xmx10m运行上述代码,得到:
[id=1,name=geym](从软引用获取对象)
After GC:
[id=1,name=geym](GC没有清除软引用指向的对象)
try to create byte array and GC (创建大数组,耗尽内存)
user id 1 is deleted (引用队列探测到对象被删除)
null (由于内存紧张,软引用虽然还在但是对象还是被回收了)
弱引用--指向的对象被GC发现即回收
弱引用是一种比软引用弱的引用类型。在GC时,只要发现弱引用的对象不管堆空间如何都会将对象回收。由于垃圾回收器的线程优先级很低,因此不一定很快发现持有弱引用的对象。这样,弱引用对象可以存在较长的时间。一旦弱引用被GC回收,就会加入一个注册的引用队列中。
弱引用例子:
/**
* 弱引用是一种比软引用较弱的引用类型。在系统gc时,只要发现弱引用,不管系统堆空间使用情况如何,
* 都会将对象进行回收。但是,由于垃圾回收期的线程通常优先级很低,并不一定很快发现持有的弱引用对象。
* 软引用和弱引用非常适合那些可有可无的缓存数据。当系统堆内存很低时,系统回收。当系统堆内存很高时,
* 这些缓存又能存在很长时间,从而起到加速系统的作用。
* Created by chenyang on 2017/2/2.
*/
public class WeakRef {
public static class User {
public User(int id, String name) {
this.id=id;
this.name=name;
}
public int id;
public String name;
}
public static void main(String[] args) {
User u=new User(1,"chenyang");
WeakReference userWeakRef=new WeakReference(u);
u=null;
System.out.println(userWeakRef.get());
System.gc();
//不管当前内存空间是否够用,都会回收它的内存
System.out.println("After GC:");
System.out.println(userWeakRef.get());
}
}
输出为:
[id=1,name=geym](从弱引用获取对象)
After GC:
null (弱对象被回收了)
软引用和虚引用的使用场景:
软引用和弱引用非常适合那些可有可无的缓存数据。当系统堆内存不足时,系统回收。当系统堆内存充足时,这些缓存又能存在很长时间,从而起到加速系统的作用。
虚引用:
虚引用是所有引用类型中最弱的一个。一个吃鱼虚引用的对象,跟没有引用几乎一样,随时会被GC回收。当试图通过虚引用的get()方法取得强引用时,总会失败。而且,虚引用必须和引用队列一起使用,它的作用在于跟踪GC回收的过程。
当GC准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序对象的回收情况。
下面给出一个例子,使用虚引用跟踪一个可复活对象的回收。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
/**
* Created by chenyang on 2017/2/2.
*/
public class TraceCanReliveObj {
public static TraceCanReliveObj obj;
static ReferenceQueue phantomQueue=null;
public static class CheckRefQueue extends Thread{
@Override
public void run() {
while (true){
if(phantomQueue!=null){
PhantomReference objt=null;
try {
objt=(PhantomReference)phantomQueue.remove();
}catch (InterruptedException e){
e.fillInStackTrace();
}
if(objt!=null){
System.out.println("TraceCanReliveObj is delete");
}
}
}
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("CanReliveObj finalize called");
obj=this;
}
@Override
public String toString() {
return "I am CanReliveObj";
}
public static void main(String[] args) throws InterruptedException{
Thread t=new CheckRefQueue();
t.setDaemon(true);
t.start();
phantomQueue=new ReferenceQueue();
obj=new TraceCanReliveObj();
PhantomReference phantomRef=new PhantomReference(obj,phantomQueue);
obj=null;
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj 是null");
}else {
System.out.println("obj 可用");
}
System.out.println("第二次gc");
obj=null;
System.gc();
Thread.sleep(1000);
if(obj==null){
System.out.println("obj是null");
}else {
System.out.println("obj 可用");
}
}
}
输出以下结果:
CanReliveObj finalize called (对象复活)
obj可用
第2次gc (第二次对象)
TraceCanReliveObj is delete (引用队列捕获到对象被回收)
obj是null