Java引用详解
来源:互联网 发布:皮革防水剂 淘宝 编辑:程序博客网 时间:2024/05/16 19:34
一、对象可达性
Java虚拟机有5个不同级别的对象的可达性。
●强可达(Strongly reachable)
如果一个对象可以被一些线程直接使用而不用通过其他引用对象(reference objects),那么它就是强可达。一个新创建的对象对创建它的线程来讲就是强可达的。
这是我们知道并且一直在使用的引用类型(译注:通常被new出来的对象都是强可达的,他们的引用就是强引用)。任何通过强引用所使用的对象(在一个活动线程中)都不会被GC回收。
●软可达(Softly reachable)
如果一个对象没有强可达性,但是它可以通过一个软引用(soft reference)来使用,那么它就具有软可达性。
只有当系统需要更多内存时,GC才会回收具有软可达性的对象。在内存不足前,GC保证一定回收软可达的对象。
有可能我们会在代码中写下这么几行:“嘿,我想要把一些数据保存在内存中。但只要JVM快把内存用光的时候,就可以直接将这些东西回收并将这些引用置为null。我会在代码里面处理这种情况。”关于软引用(SoftReference)何时应该被回收的算法依赖于不同的JVM发行版本。它往往是一个跟引用(reference)的使用频率和使用间隔有关的函数。
软引用可用来实现内存敏感的高速缓存.但是具体的行为还是得依赖于JVM。并且多少跟内存回收机制有关,保障很少并且跟具体的JVM发行版本有关。为了缓存的可靠(及其他更多特性),大多数人都会选用像Ehcache而不是用软引用实现自己的缓存。但在一些场合,使用软引用确实可以让代码非常优雅、简洁。
●弱可达(Weakly reachable)
如果一个对象既没有强可达性,也没有软可达性,但是它可以通过一个弱引用(weak reference)来使用,那么他就具有弱可达性。当弱引用指向的弱可达对象没有其他的引用,那么这个对象就会被回收。
弱引用不能阻止垃圾回收机制清理他指向的引用。弱引用最常见的使用情景是通过WeakHashMap。它是一种简单地将对象的生命周期跟Map中对象的索引域(key)绑定的方式。只有当WeakHashMap中的Key是强可达,也就是WeakHashMap中的数据域(Data域)的对象,在应用程序的其他地方有别的引用的时候,它里面的值才不会被回收。一旦应用程序中没有其他对WeakHashMap中对象的引用,那么它的所有的key就会变成弱可达,不需要用户的额外干预,所有WeakHashMap中的对象都会被清除。这是一种优雅地防止内存泄露的方式。
●虚可达(Phantom reachable)
如果一个对象既没有强可达性,也没有软可达性、弱可达性,他已经被终结(finalized),并且有一些虚引用(phantom reference)指向它,那么它就具有虚可达性。
虚引用(PhantomReference)指向的对象是不能被取回使用的。它的get()方法永远返回null,所以它有什么用呢?
所有的引用类型都允许在构造函数中指定一个引用队列(ReferenceQueue)。从语义上讲一个虚引用(PhantomReference)以什么方式、何时入队让对象终结(finalization)以一种更好、更健壮的方式进行。
●不可达(Unreachable)
当一个对象不能通过以上的方式指向,那么这个对象就变得不可达,并因此适合被回收。
二、可达性分析算法
在主流的商用程序语言(Java、C#,甚至包括前面提到的古老的Lisp)的主流实现中,都是称通过可达性分析(Reachability Analysis)来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如图3-1所示,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。
在Java语言中,可作为GC Roots的对象包括下面几种:
虚拟机栈(栈帧中的本地变量表)中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象。
本地方法栈中JNI(即一般说的Native方法)引用的对象。
三、引用的概念及应用
在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachable)状态,程序才能使用它。从JDK 1.2版本开始,把对象的引用分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用。
●强引用
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
●软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存(下文给出示例)。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。另外,一旦垃圾线程回收该Java对象之后,get()方法将返回null。
为什么要使用SoftReference?在不使用SoftReference的时候,程序所需要的数据要么保存在内存中,如果数据量比较大的话,会占用比较多的内存;要么保存在介质中,需要时进行加载,在性能上肯定比不上直接读取内存。SoftReference算是比较折中的一种解决方案,它先将数据保存在内存中,在内存不足的情况下,才将数据回收。软引用可用来实现内存敏感的高速缓存.但是具体的行为还是得依赖于JVM。并且多少跟内存回收机制有关,保障很少并且跟具体的JVM发行版本有关。为了缓存的可靠(及其他更多特性),大多数人都会选用像Ehcache而不是用软引用实现自己的缓存。
示例:
大数据对象:照片Image
- /**
- * 图片,包含大量数据
- * @author Grucee
- */
- public class Image {
- private byte[][] buffer;
- private int id;
- public Image(int id, byte[][] data) {
- this.id = id;
- buffer = data;
- }
- public int getId() {
- return this.id;
- }
- public String toString() {
- return "image[" + this.id + "]";
- }
- }
缓存管理ImageCacheManager
- /**
- * 图片缓存
- *
- * @author Grucee
- */
- public class ImageCacheManager {
- private static ImageCacheManager imageCache = null;
- private ImageCacheManager() {}
- /**
- * 作为一个管理器,当然要单例了,不允许随便new一个就当做管理器
- * @return
- */
- public static ImageCacheManager instance() {
- if (imageCache == null) {
- synchronized (ImageCacheManager.class) {
- if (imageCache == null) {
- imageCache = new ImageCacheManager();
- }
- }
- }
- return imageCache;
- }
- // 保存数据的地方
- private Map<Integer, SoftReference<Image>> cache = new HashMap<Integer, SoftReference<Image>>();
- public void put(Image image) {
- cache.put(image.getId(), new SoftReference<Image>(image));
- }
- private Image loadImage(int id) {
- byte[][] data = new byte[1024][1024];
- data[1023][1023] = 1;
- return new Image(id, data);
- }
- public Image get(int id) {
- SoftReference<Image> ref = cache.get(id);
- // 没有放入过缓存,第一次加载
- if (ref == null) {
- Image loadFirst = loadImage(id);
- // 放入缓存
- put(loadFirst);
- return loadFirst;
- }
- // 下面的情况是该图片已经被加载过,但是可能由于内存不足,又被回收了
- // 为了便于理解,使用了if else分支方式,其实else不是必须的。
- // 这两个分支,不管哪一个返回的都是对image的强引用
- Image cachedImage = ref.get();
- if (cachedImage == null) {
- // 从真实介质中读取(此处模拟这个操作)
- Image imageGetFromMedia = loadImage(id);
- // 缓存起来
- put(imageGetFromMedia);
- System.out.println("get image:" + id + " from media.");
- return imageGetFromMedia;
- } else {
- System.out.println("get image:" + id + " from cache.");
- return cachedImage;
- }
- }
- }
测试类
- /**
- * 测试类
- * @author Grucee
- */
- public class SoftReferenceTest
- {
- private ImageCacheManager cacheMgr = ImageCacheManager.instance();
- /**
- * 设置启动参数
- * @param args
- */
- public static void main(String[] args) {
- SoftReferenceTest tester = new SoftReferenceTest();
- //先把数据load进来
- for (int i = 0; i < 10; i++) {
- tester.cacheMgr.get(i);
- }
- //一张特殊的图片,这里保存了一个对这张图片的强引用,所以该对象不会被回收
- Image myPhoto = tester.cacheMgr.get(7);
- System.out.println("--------------------------");
- System.out.println(myPhoto);
- System.out.println("--------------------------");
- //打印图片
- for (int i = 0; i < 10; i++) {
- tester.printImage(i);
- }
- }
- public void printImage(int id) {
- System.out.println(cacheMgr.get(id));
- }
- }
启动设置内存堆大小为10M
-Xmx10M -Xms10M -verbose:gc
测试一:
当测试类SoftReferenceTest中特殊图片是第8张是,运行结果
[GC (Allocation Failure) 2048K->1972K(9728K), 0.0048794 secs]
get image:8from cache.
--------------------------
image[8]
--------------------------
[GC (Allocation Failure) 2898K->3066K(9728K), 0.0019205 secs]
get image:0from media.
image[0]
get image:1from media.
image[1]
[GC (Allocation Failure) 5114K->5338K(9728K), 0.0007850 secs]
[Full GC (Ergonomics) 5338K->4866K(9728K), 0.0032548 secs]
get image:2from media.
image[2]
get image:3from media.
image[3]
[GC (Allocation Failure) 6914K->7114K(8704K), 0.0006606 secs]
[Full GC (Ergonomics) 7114K->6907K(8704K), 0.0028911 secs]
get image:4from media.
image[4]
[Full GC (Ergonomics) 7931K->7912K(8704K), 0.0075340 secs]
get image:5from media.
image[5]
[GC (Allocation Failure) 3014K->3022K(9216K), 0.0004105 secs]
get image:6from media.
image[6]
[GC (Allocation Failure) 4045K->4118K(9216K), 0.0011056 secs]
get image:7from media.
image[7]
get image:8from cache.
image[8]
[GC (Allocation Failure) 5141K->5262K(9216K), 0.0005411 secs]
get image:9from media.
image[9]
测试二:
当测试类SoftReferenceTest中特殊图片是第7张是,运行结果
[GC (Allocation Failure) 2048K->1972K(9728K), 0.0048794 secs]
get image:7from media.
--------------------------
image[7]
--------------------------
[GC (Allocation Failure) 2898K->3066K(9728K), 0.0019205 secs]
get image:0from media.
image[0]
get image:1from media.
image[1]
[GC (Allocation Failure) 5114K->5338K(9728K), 0.0007850 secs]
[Full GC (Ergonomics) 5338K->4866K(9728K), 0.0032548 secs]
get image:2from media.
image[2]
get image:3from media.
image[3]
[GC (Allocation Failure) 6914K->7114K(8704K), 0.0006606 secs]
[Full GC (Ergonomics) 7114K->6907K(8704K), 0.0028911 secs]
get image:4from media.
image[4]
[Full GC (Ergonomics) 7931K->7912K(8704K), 0.0075340 secs]
get image:5from media.
image[5]
[GC (Allocation Failure) 3014K->3022K(9216K), 0.0004105 secs]
get image:6from media.
image[6]
[GC (Allocation Failure) 4045K->4118K(9216K), 0.0011056 secs]
get image:7from cache.
image[7]
get image:8from media.
image[8]
[GC (Allocation Failure) 5141K->5262K(9216K), 0.0005411 secs]
get image:9from media.
image[9]
测试一和测试二对比
首先我们通过启动参数-Xmx10M -Xms10M -verbose:gc,将jvm的堆内存大小设置为10M。
现在我们有10张照片,每张1M大小。由于JVM堆中还有方法区(存放类定义)、常量池等,所以这10张照片是不能够全部存放在cache中的。
通过上面测试一和测试二的对比我们可以看出,当全部加载10张图片后,我们获取第八张图片是从cache中取的;获取第七张图片时,是重新加载的(从media中加载)。所以实际上,由于内存有限,我们的cache只是在内存中保存了2张照片;之前加载的照片由被回收了,通过[GC (Allocation Failure) 6914K->7114K(8704K), 0.0006606 secs]可以看出进行了内存回收。
所以,我们这里实现的cache在内存不足的时候,会自动被回收的。
之后我们强引用了一张图片,可以看出存在强引用的这张图片是一直存在内存中的,不会被回收。
●弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
弱引用最常见的用处是在集合类中,尤其在哈希表中。哈希表的接口允许使用任何Java对象作为键来使用。当一个键值对被放入到哈希表中之后,哈希表对象本身就有了对这些键和值对象的引用。如果这种引用是强引用的话,那么只要哈希表对象本身还存活,其中所包含的键和值对象是不会被回收的。如果某个存活时间很长的哈希表中包含的键值对很多,最终就有可能消耗掉JVM中全部的内存。
对于这种情况的解决办法就是使用弱引用来引用这些对象,这样哈希表中的键和值对象都能被垃圾回收。Java中提供了WeakHashMap来满足这一常见需求。
示例:
public staticvoid main(String[] args)
{
Map<Integer, String> map = new WeakHashMap<Integer, String>();
Integer key = new Integer(1);
map.put(key, "test");
// key不再有强引用
key = null;
System.gc();
//等待一段时间,进行垃圾回收
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(map.size());
}
输出:
[GC (Allocation Failure) 509K->520K(130560K), 0.0028151 secs]
[GC (System.gc()) 746K->560K(131072K), 0.0007948 secs]
[Full GC (System.gc()) 560K->513K(131072K), 0.0091938 secs]
0
可见当WeakHashMap中key不存在强引用时,随时都会从map中移除。
●虚引用(PhantomReference)
1、 基本概念
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
2、析构finalization
Java中内存管理是自动进行的。开发者无需关心已经释放的对象的内存回收。这样的一个缺点是开发者无法得知某个特定对象何时被回收;另外,开发者无法控制内存管理。不过,java.lang.ref包定义了一些可以和垃圾回收器进行有限交互的类。具体的类是SoftReference,WeakReference以及PhantomReference,它们是Reference的子类,以不同的方式和垃圾回收器交互。
有时需要在对象被回收前做一些清理工作,可以使用析构方法。该特性可以用来回收对象相关的本地资源。但是,Java中的析构方法有很多的问题。
第一:我们无法预期finalize()何时会被调用。没有任何保证某个对象一定会被垃圾回收。一个在JVM整个生命周期中可达的对象永远不会被回收;也有可能,在对象变成可回收之后和JVM停止之前,垃圾回收线程没有运行。
第二:Java析构方法会将应用程序拖慢。管理重载了finalize()方法的对象需要耗费JVM更多的资源。
第三:对象即使被调用了finalize()方法,也无法保证该对象一定会被回收;并且,可以在finalize()方法中对对象进行拯救(重新赋予强引用)。
虽然有可达性分析算法来判定对对象状态,但这并不是对象是否被回收的条件,对象回收的条件远远比这个复杂,比如无法通过ROOT找到的对象,也不一定会回收,会进入一个死缓的阶段,那些无法通过根节点引用链找到的对象,会被第一次标记,并进行一次筛选,筛选条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法,或者finalize() 方法已经被虚拟机调用过,虚拟机都视为“没必要执行”。
如果该对象被判定为有必要执行finalize(),那么对象会被放置在一个F-Queue 的队列中,并由一个优先级较低的Finalizer线程去执行,这里的执行是 JVM 会触发这个方法,但并不保证等待他运行结束,因为finalize() 方法执行慢,或者死循环,会影响该队列其他元素执行。
执行finalize() 方法就会进行第二次标记,然后等待JVM 进行回收了,而在finalize() 方法执行的同时,可以对对象进行“拯救”,也就是说在执行方法内部,再次对对象进行引用,那么对象就复活了。
示例:- public class SaveFinalizedObject {
- private static class User {
- public static Resource res = null;
- }
- private static class Resource {
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- System.out.println("执行finalize方法,并拯救对象");
- User.res = this;
- }
- }
- public static void main(String[] args) throws InterruptedException {
- // 使用者占用资源
- User.res = new Resource();
- // 使用者释放资源,这时上面new的Resource对象已经没有强引用,可以被垃圾回收
- User.res = null;
- // 此处如果执行了finalize方法后,对象即可被拯救成功,又有了强引用
- System.gc();
- Thread.sleep(500); // gc线程优先级比较低,此处等待垃圾回收
- System.out.println(User.res);
- // 再次释放资源,让资源被回收(此时已经不能被拯救,因为finalize方法只会被调用一次)
- User.res = null;
- Thread.sleep(500);
- System.out.println(User.res);
- }
- }
[GC (Allocation Failure) 509K->504K(130560K), 0.0008240 secs]
[GC (System.gc()) 739K->568K(131072K), 0.0019017 secs]
[Full GC (System.gc()) 568K->513K(131072K), 0.0067556 secs]
执行finalize方法,并拯救对象
grucee.test.Main$Resource@15db9742
null
强烈建议不要重写Object的finalize()方法。
3、虚引用—何时使用虚引用?
当理智告诉我们不应该使用finalize(由于上面一系列的缺点),可以考虑使用虚引用。该类型的引用和java.lang.ref包中定义的其他引用不同,因为它不是用来访问对象的,只是作为一个对象已经被析构、垃圾回收期准备回收它的内存的信号。
正如API文档中所说:
虚引用对象,在垃圾回收器确定它所引用的对象要被回收之后,会被放入队列。虚引用常被用来以比JAVA析构机制更灵活的方式执行清理动作。
虚引用是一种比较安全的知道对象已经从内存中移除的方式。例如,考虑一个处理大图片的应用程序。我们希望内存中已经存在的大图片在被垃圾回收期准备回收后,才去加载新的大图片。这种情况下,虚引用是一种灵活、安全的选择。旧的图片对象在析构后,会被放在ReferenceQueue引用队列中;在队列中可获取该引用后,我们就可以加载新的大图片到内存中了。
示例:
- /**
- * 虚引用测试用例
- * @author Grucee
- */
- public class PhantomReferenceTest {
- private static class Connection {
- private int resourceId;
- private Connection(int resourceId) {
- System.out.println("分配资源:" + resourceId);
- this.resourceId = resourceId;
- }
- public int getResourceId() {
- return this.resourceId;
- }
- }
- private static class ConnectionMonitor extends PhantomReference<Connection> {
- private int resourceId;
- private ReferenceQueue<Connection> queue;
- public ConnectionMonitor(Connection referent, ReferenceQueue<Connection> queue) {
- super(referent, queue);
- this.resourceId = referent.getResourceId();
- this.queue = queue;
- }
- public void waitToCleanUP() {
- try {
- //此处会阻塞一直到被引用的connection被回收
- queue.remove();
- this.clear();
- //资源清理
- System.out.println("清理资源:" + this.resourceId);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 上个连接的资源回收之后,才允许分配下一个连接
- * @author Grucee
- */
- public static class ConnectioinManager {
- private static ConnectionMonitor monitor = null;
- private ConnectioinManager(){}
- public synchronized static Connection openConnection(int resourceId) {
- if (monitor != null) {
- monitor.waitToCleanUP();
- }
- Connection conn = new Connection(resourceId);
- ReferenceQueue<Connection> queue = new ReferenceQueue<Connection>();
- monitor = new ConnectionMonitor(conn, queue);
- return conn;
- }
- }
- public static void main(String[] args) {
- Connection conn = ConnectioinManager.openConnection(101);
- //如果不添加这一行,会发现一直得不到102资源
- conn = null;
- System.gc();
- conn = ConnectioinManager.openConnection(102);
- }
- }
- Java引用类型详解
- Java引用详解
- Java引用详解
- Java弱引用详解
- Java引用详解
- java中的引用详解
- Java引用类型详解
- Java强引用、软引用、弱引用、虚引用详解
- Java强引用、软引用、弱引用、虚引用详解
- Java-强引用、软引用、弱引用、虚引用详解
- Java强引用、软引用、弱引用、虚引用详解
- Java强引用、软引用、弱引用、虚引用详解
- Java强引用、软引用、弱引用、虚引用详解
- Java强引用、软引用、弱引用、虚引用详解
- java 强引用,软引用,弱引用,虚引用 详解
- Java中引用的详解
- Java四种引用详解
- Java中四种引用类型详解
- zoj 3811 (2014 牡丹江网赛 C) Untrusted Patrol
- linux下 实时网络流量监控命令
- QTableWidgetItem类文档
- Candy
- Problem-A 老衲的嘱托
- Java引用详解
- 使用sar进行性能分析
- MFC开发自己的截图工具
- 算法:找出数组中未出现的那个数字
- 在Visual C++中用ADO进行数据库编程
- Android 判断Service是否已经运行
- zoj 3811 Untrusted Patrol(dsf+并査集+邻接表)
- 数据结构之排序算法整理(2)
- leetcode-Linked List Cycle I & II