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<User> userSoftRef=new SoftReference<User>(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<User> 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<User>{
        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<User>();
        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<User> userWeakRef=new WeakReference<User>(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<TraceCanReliveObj> phantomQueue=null;
    public static class CheckRefQueue extends Thread{
        @Override
        public void run() {
            while (true){
                if(phantomQueue!=null){
                    PhantomReference<TraceCanReliveObj> objt=null;
                    try {
                        objt=(PhantomReference<TraceCanReliveObj>)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<TraceCanReliveObj>();
        obj=new TraceCanReliveObj();
        PhantomReference<TraceCanReliveObj> phantomRef=new PhantomReference<TraceCanReliveObj>(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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • 1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供...
    简欲明心阅读 89,496评论 17 311
  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,970评论 2 31
  • [TOC] 内存管理 一、托管堆基础 在面向对象中,每个类型代表一种可使用的资源,要使用该资源,必须为代表资源的类...
    _秦同学_阅读 3,808评论 0 3
  • Android 内存管理的目的 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。简单粗...
    晨光光阅读 1,294评论 1 4
  • 让我们先看看关于爆款的几个问题: 为何浙江卫视投入巨资打造《中国好声音》?为何现在国内足球俱乐部花大价钱引入大牌球...
    金刚king阅读 2,163评论 0 50