0030 Java学习笔记-面向对象-垃圾回收、(强、软、弱、虚)引用
2016-11-24 15:08
399 查看
垃圾回收特点
垃圾:程序运行过程中,会为对象、数组等分配内存,运行过程中或结束后,这些对象可能就没用了,没有变量再指向它们,这时候,它们就成了垃圾,等着垃圾回收程序的回收再利用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"了 } }
相关文章推荐
- Java 学习笔记 (13) - 基本内存分析 和 垃圾回收机制
- Java基础复习笔记 对象状态、引用种类、垃圾回收形式02
- JVM学习笔记(1、 基本结构;2、Java代码编译和执行的整个过程3、内存管理和垃圾回收 4、 内存调优 )
- JAVA学习笔记9——垃圾回收机制+构造方法+重载
- java学习之旅33--面向对象_06_虚拟机内存管理_垃圾回收机制_c++和java的比较
- Java之学习笔记(24)-----------垃圾回收机制
- Java学习笔记--垃圾回收GC
- Java学习笔记之垃圾回收机制
- Java编程思想学习笔记_1(Java内存和垃圾回收)
- java学习个人笔记---内存管理之垃圾回收基本算法
- java学习笔记4_垃圾回收机制
- Java学习笔记之垃圾回收
- Java学习笔记之垃圾回收
- Java补完之垃圾回收GC机制学习笔记
- Java垃圾回收学习笔记
- java 垃圾回收机制(GC)学习笔记
- python 基础学习笔记(四) 引用及垃圾回收
- 【java基础知识(学习笔记)】--引用数据类型
- 【java基础知识(学习笔记)】--面向对象
- Java学习笔记十八:Java面向对象的三大特性之封装