垃圾回收特点

  • 垃圾:程序运行过程中,会为对象、数组等分配内存,运行过程中或结束后,这些对象可能就没用了,没有变量再指向它们,这时候,它们就成了垃圾,等着垃圾回收程序的回收再利用
  • Java的垃圾回收机制只回收堆内存中的对象,不回收数据库连接、IO等资源,所以才要在finally中关闭
  • 要回收,但什么时候回收是不一定的,即使显式的调用了System.gc()
  • 垃圾回收程序在真正回收之前,会先调用被回收对象的finalize()方法,这是Object的protected方法,每个类都要继承的,这个方法可能导致这个被回收对象复活,就是让另一个引用指向它,因而就取消回收。此处存疑?垃圾回收程序是否一定会执行finalize()方法,还是不一定?
  • 特别注意:垃圾回收不会回收在常量池中的String的直接量

对象在内存中的状态

  • 可达状态:如果一个对象有一个或以上的引用指向它,那它就是可达的,可以通过引用变量访问到它的实例变量、实例方法等
  • 可恢复状态:没有引用指向某个对象了,那它就成了可恢复状态。可恢复的意思是调用finalize()方法时,可能能让一个引用变量重新指向它,从而变成可达状态
  • 不可达状态:调用finalize()方法后,也没能让一个对象变回可达状态,那这个对象就处于不可达状态,然后就等着被真正的回收吧

对象被哪些变量引用

  • 被类变量引用:该类被销毁,被引用对象才变为可恢复
  • 被实例变量引用:该实例变量所属的对象被销毁,被引用对象才变为可恢复
  • 被局部变量引用:切换二者的指向关系时,被引用对象变为可恢复

何时回收

  • 垃圾回收程序会不定时的运行,我们完全不能把握,最多就是通知它一声“麻烦你来回收垃圾吧”

    • System.gc()
    • Runtime.getRuntime().gc():每个Java程序都有一个与之对应的Runtime实例,应用程序通过该类与其运行环境关联,但不能创建自己的Runtime实例,但可以通过静态方法getRuntime()返回一个。
  • 比如下面的代码,我运行了20次,垃圾回收也没运行一次
package testpack;
public class Test1{
    public static void main(String[] args) {
        for (int i=0;i<4;i++){
            new Test1();
        }
    }
    public void finalize(){
        System.out.println("垃圾回收前的finalize()方法运行中");
    }
}
  • 再运行下面的代码,加了一行System.gc(),就是每次创建对象之后都通知一次gc(),结果运行的次数0~4次都有
package testpack;
public class Test1{
    public static void main(String[] args) {
        for (int i=0;i<4;i++){
            new Test1();
            System.gc();                         //增加了这一行,通知gc()回收垃圾
        }
    }
    public void finalize(){
        System.out.println("垃圾回收前的finalize()方法运行中");
    }
}

finalize()方法

  • Java的垃圾回收和finalize()方法比较复杂,看:http://www.cnblogs.com/iamzhoug37/p/4279151.html
  • 该方法是Object的protected方法,每个类都会继承
  • 不要自己调用该方法,该方法应该只由垃圾回收机制调用
  • finalize()方法不一定何时被调用,还有可能不会被调用?存疑!
  • finalize()方法运行后,可能使一个对象从可恢复变为可达
  • finalize()方法出现异常的话,垃圾回收机制不报异常,继续运行
  • 看示例代码:
package testpack;
import java.io.IOException;
import java.io.PrintStream;
public class Test1{
    public static void main(String[] args) throws IOException{
        PrintStream ps=new PrintStream("E:\\Temp\\output.txt");
        PrintStream ini=System.out;      //将标准输出保存下来,后面才好恢复
        System.setOut(ps);               //下面的输出比较长,控制台显示不全,将标准输出重定向到output.txt
        for (int i=1;i<=10000;i++){
            new Test1(i);                //对象创建后就进入可恢复状态
            System.gc();                 //通知系统垃圾回收,何时回收不确定
            System.runFinalization();    //该方法是强制系统立即调用可恢复对象的finalize()方法,跟System.gc()配合使用,这里进行了10000次循环,每次都运行了finalize()方法,但API文档中写的是`suggest`,不是一定。如果不调用这个方法,finallize()方法不一定何时调用。此处比较复杂,本例未必正确。
            System.out.println("当前运行第 "+i+" 轮循环"+"  当前t1指向第  "+t1.num+"   个循环创建的对象");
        }
        ps.close();
        System.setOut(ini);
        System.out.println("运行结束");
    }
    public int num;
    public Test1(int num){
        this.num=num;
    }
    private static Test1 t1=null;
    public void finalize(){
        t1=this;                          //finalize()方法运行后,可恢复对象变为可达对象
    }
}

强、软、弱、虚引用

  • 强引用StrongReference:

    • 常见的一般都是这种强引用
  • 软引用SoftReference:
    • 软引用指向的对象,有可能被回收,看内存够不够,不够的话,有可能回收
    • 多用于内存敏感的程序中
    • 通过SoftReference类实现
    • 包含一个get()方法,用来获得被它引用的对象
  • 弱引用WeakReference:
    • 跟软引用很像,但比弱引用还低,系统回收垃圾时,不管内存够不够,都要回收弱引用
    • 通过WeakReference类实现
    • 包含一个get()方法,用来获得被它引用的对象
  • 虚引用PhantomReference:
    • 虚引用就跟没有被引用差不多,主要用于跟踪对象被回收的状态
    • 不能单独使用,得跟引用队列java.lang.ref.ReferenceQueue联合使用
    • 包含一个get()方法,但总是返回null,就是不想你再"复活"一个对象
  • java.lang.ref.ReferenceQueue
    • 用于保存被回收后对象的引用
    • 系统在回收被引用的对象之后,将把被回收对象对应的引用添加到关联的引用队列中
  • 弱引用示例:
package testpack;
import java.lang.ref.WeakReference;
public class Test1{
    public static void main(String[] args){
        String s1=new String("ABCDEFG");
        WeakReference wf=new WeakReference(s1);
        s1=null;                                 //切断强引用
        System.out.println(wf.get());            //通过get()方法获得引用的对象
        System.gc();
        System.runFinalization();
        System.out.println(wf.get());            //返回null
    }
}
  • 虚引用示例:跟ReferenceQueue配合使用
package testpack;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class Test1{
    public static void main(String[] args){
        String s1=new String("ABCDEFG");
        ReferenceQueue rq=new ReferenceQueue();
        PhantomReference pr=new PhantomReference(s1,rq);
        s1=null;                                  //切断强引用,保留了虚引用
        System.out.println(pr.get());             //返回null,虚引用的get()方法总是返回null,不能返回被引用对象
        System.gc();
        System.runFinalization();                 //finalize()运行后,虚引用对象将被添加到引用队列上
        System.out.println(rq.poll()==pr);        //.poll()表示引用队列上的第一个被引用对象
    }
}

关于String常量池的再思考

  • 之前在:0024 Java学习笔记-面向对象-包装类、对象的比较、String常量池问题中说过,String的intern()方法的作用是:返回这个字符串在常量池中的地址,常量池中如果有了这个字符串,那就返回其地址;如果没有,那就这个字符串在堆内存中的地址,添加到常量池表里面,再返回其地址,相当于返回的地址通过常量池表间接的指向了堆内存中的地址。
  • 下面做进一步的验证:
  • 先看代码:
package testpack;
public class Test1{
    public static void main(String[] args){
        String s1="A";
        String s2="B";
        String s3=s1+s2;
        System.out.println("AB"==s3);          //返回false,说明s3=s1+s2这行代码创建的"AB"对象位于堆内存中,否则应该返回ture
    }
}
  • 再看第二段代码
package testpack;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class Test1{
    public static void main(String[] args){
        String s1="A";
        String s2="B";
        String s3=s1+s2;                           //s3指向堆内存中的"AB"
        String s4=s3.intern();                     //intern()方法将堆内存中的"AB"的地址添加到常量池表中,s4通过常量池表间接的指向堆内存中的"AB"
        System.out.println(s3==s4);                //true。s3和s4直接和间接指向堆内存的"AB",因而返回ture
        System.out.println(s4=="AB");              //true。此处的直接量"AB"实际指向了堆内存中的"AB",因而返回true
        ReferenceQueue rq=new ReferenceQueue();
        PhantomReference pr=new PhantomReference(s3,rq); //创建虚引用,将堆内存中的"AB"跟引用队列关联起来
        s3=null;                                         //切换s3和堆内存中"AB"对象的强引用
        System.gc();
        System.runFinalization();                        //这里将会运行finalize()方法,只有虚引用的对象都将被添加到引用队列上。如果堆内存中的"AB"的强引用全部被切断,那么"AB"将被添加到队列上
        System.out.println(s4);                          //输出"AB"。说明堆内存中的"AB"还存在
        System.out.println(rq.poll()==pr);               //输出false。说明运行了finalize()方法后,堆内存中的"AB"没有被添加到引用队列上,从而说明还有强引用指向它,那就是s4.

        //如果没有s4=s3.intern()这句,再把下面包含s4的语句全注释掉,最后一行输出true,说明没有强引用指向"AB"了
    }
}

0030 Java学习笔记-面向对象-垃圾回收、(强、软、弱、虚)引用的更多相关文章

  1. java中强,软,弱,虚引用 以及WeakHahMap

    java中强,软,弱,虚引用  以及WeakHahMap   一:强软引用: 参考:http://zhangjunhd.blog.51cto.com/113473/53092/进行分析   packa ...

  2. java中的强,软,弱,虚引用

    引用的应用场景 我们都知道垃圾回收器会回收符合回收条件的对象的内存,但并不是所有的程序员都知道回收条件取决于指向该对象的引用类型.这正是Java中弱引用和软引用的主要区别. 如果一个对象只有弱引用指向 ...

  3. Java虚拟机学习笔记——JVM垃圾回收机制

    Java虚拟机学习笔记——JVM垃圾回收机制 Java垃圾回收基于虚拟机的自动内存管理机制,我们不需要为每一个对象进行释放内存,不容易发生内存泄漏和内存溢出问题. 但是自动内存管理机制不是万能药,我们 ...

  4. 0028 Java学习笔记-面向对象-Lambda表达式

    匿名内部类与Lambda表达式示例 下面代码来源于:0027 Java学习笔记-面向对象-(非静态.静态.局部.匿名)内部类 package testpack; public class Test1{ ...

  5. 0025 Java学习笔记-面向对象-final修饰符、不可变类

    final关键字可以用于何处 修饰类:该类不可被继承 修饰变量:该变量一经初始化就不能被重新赋值,即使该值跟初始化的值相同或者指向同一个对象,也不可以 类变量: 实例变量: 形参: 注意可以修饰形参 ...

  6. 0013 Java学习笔记-面向对象-static、静态变量、静态方法、静态块、单例类

    static可以修饰哪些成员 成员变量---可以修饰 构造方法---不可以 方法---可以修饰 初始化块---可以修饰 内部类(包括接口.枚举)---可以修饰 总的来说:静态成员不能访问非静态成员 静 ...

  7. (转)《深入理解java虚拟机》学习笔记3——垃圾回收算法

    Java虚拟机的内存区域中,程序计数器.虚拟机栈和本地方法栈三个区域是线程私有的,随线程生而生,随线程灭而灭:栈中的栈帧随着方法的进入和退出而进行入栈和出栈操作,每个栈帧中分配多少内存基本上是在类结构 ...

  8. java学习之(垃圾回收)

    程序无法精确控制java垃圾回收的时机,但依然可以强制系统进行垃圾回收--这种强制只是通知系统进行垃圾回收, 但系统是否进行垃圾回收依然不确定.大部分时候,程序强制系统垃圾回收后总会有一些效果,强制系 ...

  9. 0020 Java学习笔记-面向对象-变量

    变量分为哪些 成员变量:类里面,方法外面定义的变量 实例变量:没有用static修饰的变量,属于对象:存在期:创建实例-销毁实例:作用域:与该实例的生存范围相同 类变量:用static修饰的变量,属于 ...

随机推荐

  1. 【绝对干货】仿微信QQ设置图形头像裁剪,让你的App从此炫起来~

    最近在做毕业设计,想有一个功能和QQ一样可以裁剪头像并设置圆形头像,额,这是设计狮的一种潮流. 而纵观现在主流的APP,只要有用户系统这个功能,这个需求一般都是在(bu)劫(de)难(bu)逃(xue ...

  2. 选盘秘籍:用户如何选择SSD/SATA/SAS?

    先学习下一些专业词汇 IDE (Integrated Drive Electronics) 电子集成驱动器 它的本意是指把"硬盘控制器"与"盘体"集成在一起的硬 ...

  3. VB.NET 注册表基本操作

    ''' <summary> ''' 注册表设置值 ''' </summary> ''' <param name="strKey"></pa ...

  4. Extjs4.2纯前台导出Excel总结

    前段时间做了两个项目,用到了Extjs4.2纯前台导出Excel,遇到很多的问题,从网上也找了很多资料,在这里总结一下,做一个记录: 使用方法: 1.下载extexcel文件包,这里可以下载http: ...

  5. Linux 2.4调度系统分析--转

    http://www.ibm.com/developerworks/cn/linux/kernel/l-k24sch/index.html 杨沙洲 (pubb@163.net)国防科技大学计算机学院 ...

  6. 设计模式Day02

    1.生成器模式 生成器模式也称为建造者模式.生成器模式的意图在于将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示. 生成器模式的编程步骤: (1)定义一个产品类:  由于不在该类完 ...

  7. 算法-KMP模式匹配算法

    1朴素算法:逐个比较 2 主要是解决多余比较的麻烦,通过处理比较字符串是否含有重复的字符的问题.

  8. java:Comparable比较器

    /*Comparable 是java.lang中的一个接口,所以是默认导入的,不需要显示的导入. *如果你先直接在本类中实现排序,那么可以直接实现该接口(例如:public class Compara ...

  9. 元组tuple插入字符串的方式

    元组无法更改,但是可以用切片的方式将头尾切出,中间'+'字符串,后整体赋值原先的元组,举例如下 >>> temp=('东邪','西毒' ,'南帝') >>> tem ...

  10. BZOJ4025 二分图 分治 并查集 二分图 带权并查集按秩合并

    原文链接http://www.cnblogs.com/zhouzhendong/p/8683831.html 题目传送门 - BZOJ4025 题意 有$n$个点,有$m$条边.有$T$个时间段.其中 ...