JVM学习(6)jvm如何判断垃圾

一.概述:

垃圾回收的方法是判断没有个对象的可触及性,即从根节点开始是否可以访问到这个对象。如果访问到了,说明当前对象正在使用,如果从所以根节点都无法访问到某个对象,说明这个对象不再使用了。一般情况下,此对象要被回收。但是,一个无法触及的对象有可能在某一个条件下复活自己,那么对它回收就不合理了。因此,需要给出一个对象可触及性状态的定义,并规定在什么状态下,才可以安全回收对象。
可触及性包括三种:
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包中找到。如下显示了三种引用对应的类。


image.png

强引用--指向的对象不会被回收

强引用就是程序中一般使用的引用类型,强引用的对象时可触及的,不会被回收。但是,软引用,弱引用和虚引用的对象是软可触及,弱可触及和虚可触及,在一定的条件下,是可以被回收的。
下面是个强引用例子:

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实例的强引用。


图片发自App

强引用特点:

  • 强引用可以直接访问目标对象。
  • 强引用所指向的对象在任何时候都不会被系统回收,宁可抛出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 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

你可能感兴趣的:(JVM学习(6)jvm如何判断垃圾)