引用

11 篇文章 0 订阅
1 篇文章 0 订阅

        在java/android中应该注意,虽然平时使用时基本上使用的都是StrongReference强引用,但是其他的引用其实反而更加重要,因为这些引用的使用反而可以使得程序更加的优化,简洁,所以对于引用的使用的掌握是很重要的。这里引用有StrongReference,SoftReference,WeakReference,PhantomReference四类。

        掌握引用需要掌握StrongReference,SoftReference,WeakReference,PhantomReference,RefenrenceQueue,LinkedHashMap,ConcurrentHashMap等相关类,这样可以优化内存。

        StrongReference强引用是最常见的引用,这种引用基本上很难被GC清理的,除非真的没用了,不然GC宁愿抛出OOM也不愿释放他们的内存。

        SoftReference软引用是很重要的引用类型,这种引用在内存足够时,不会被GC,而在内存不足时会被回收,常常用于内存敏感的高速缓冲中,在内存被回收之后会被放到引用队列RefenrenceQueue里面。也可以RefenrenceQueue作为SoftReference参数。注意RefenrenceQueue给必须使用。

        WeakReference弱引用只要是GC一旦发现就会回收,不管内存是否足够,在内存回收之后会被放到引用队列RefenrenceQueue里面。也可以RefenrenceQueue作为SoftReference参数。这里WeakReference弱引用可以用于督促GC的回收。同时注意GC未必会马上回收,因为有可能没发现。注意RefenrenceQueue给必须使用。

        PhantomReference虚引用就是形同虚设的引用,他不会决定引用对象的生命周期,GC任何时刻都可以回收它。PhantomReference主要用来跟踪对象被GC的活动。这里注意,PhantomReference与前面的引用是不同的,它必须要与RefenrenceQueue结合使用,并且再回首内存对象之前先回收PhantomReference。

        这里引用涉及到一个可及性的问题,可及性就是引用还可以得到对象进行操作。除了虚引用,每一个引用都是可以操作所引用的对象的,而一个对象可能被多个对象引用,这是注意,分为两种情况讨论:1.单条引用路径的最弱的引用决定对象的可及性。2.多条引用路径的最强的引用决定对象的可及性。

        这里注意RefenrenceQueue如果作为引用的参数,那么他在使用引用的get()方法返回的是null时,将会把引用注册到自己里面,可以使用RefenrenceQueue.poll()返回引用并删除。




        而实际上在真正使用Reference的时候,其实并没有像上面说明的那么实用,因为Java/android使用的垃圾回收机制是有JVM自动回收的,我们能做的仅仅是促进,而不是主导。也就是说,回收垃圾是GC的事,我们仅仅提醒他可以回收了。

        对于SoftReference,由于只有在内存不足时,才会释放自身所包含的内存空间,所以实际上SoftReference实际使用时除非遇到内存不足的情况,不然使用起来就和强引用是一样的。

        对于WeakReference,如果它输入的参数是方法里面的,也就是局部变量,那么就有两种情况:1.使用Object obj=new Object();WeakReference<Object> reference=new WeakReference<Object>(obj);进行实例化。这个时候,只需要调用obj=null;System.gc();就可以在这个方法里面直接将内存释放掉。当然,也可以在这个方法里面使用obj=null;然后在后面调用的方法里面使用System.gc();这样也是可以的。2.使用WeakReference<Object> reference=new WeakReference<Object>(new Object());进行实例化,这个时候就需要在下一个调用的方法里面加上System.gc();然后内存就释放了。总的来说,其实上面的情况利用的都是GC一发现弱引用就会回收的原理进行的,调用System.gc();就是通知GC,然后GC就会发现弱引用并回收了。

        对于WeakReference,如果输入的参数如果是全局变量,那么会出现以下两种情况:1.在WeakReference所在的方法里面使用obj=null;System.gc();并不能释放内存。这个时候可以在这个方法里面调用obj=null;然后在下一个方法里面调用System.gc();这样可以实现内存释放。2.在WeakReference所在的方法里面初始化,然后在下一个方法里面设置obj=null;System.gc();这样也是可以释放内存的。

        总的来说,其实对于一个方法,在运行完方法之后,如果方法里面的对象没用了,那么就会被回收。基于这种原理,WeakReference在他所在的方法里面将实例化时输入的对象设为null,然后在下一个方法调用时调用System.gc();就可以释放内用的对象的内存了。而如果使用WeakReference<Object> reference=new WeakReference<Object>(new Object());那么连设置输入参数的对象null这一步都省了,因为这个对象只是单纯的弱引用,GC一发现弱引用就马上回收,所以在下一个方法调用System.gc();时内存马上就被回收了。最特别的是使用obj=null;System.gc();这一种情况,其实这种情况和使用WeakReference<Object> reference=new WeakReference<Object>(new Object());是一样的,因为obj=null;之后WeakReference输入的参数对象就只剩下弱引用,而没有其他引用了,这就和WeakReference<Object> reference=new WeakReference<Object>(new Object());是一样的。

        其实上面的三种情况有着共同点,那就是作用域(这里讨论的是引用中输入参数对象的作用域)。对于局部变量,所作用领域就是方法的作用域,而方法用完之后系统会回收方法中没用的对象的内存,所以回收局部变量的内存空间可以在方法里面进行(当然也可以在下一个方法里面进行)。而全局变量则不同,它的作用范围是全局的,所以需要在变量的操作完成之后,在下一个方法告诉GC可以回收了,也就是说,我们可以在当前方法告诉GC,引用的对象初始化了,然后可以在当前方法或者下一个方法设置引用输入参数的对象为null,但是必须要在下一个方法设置System.gc();。其实对于全局变量,之所以要在下一个方法调用System.gc();是因为区域的关系,我们需要在主区域,也就是全局变量区域之外告诉GC进行回收。注意,主区域里面一般不进行回收,主区域调用的方法是局部域,局部域只有在用完之后回收,而主区域之所以不回收是因为程序往下运行需要前面的东西。



        对于PhantomReference,PhantomReference.get()永远返回null,所以我们使用new PhantomReference(Object,ReferenceQueue)中的ReferenceQueue的poll(),或者remove()等方法判断是否回收,同时注意,这些方法基本上仅仅返回一次不是null的,因为这个时候PhantomReference被放进ReferenceQueue里面进行回收,后面ReferenceQueue就又空了,所以我们就只能看到一次返回不为null。

        PhantomReference适合使用在追踪垃圾回收的场合,因为WeakReference是在内存回收之后被放入ReferenceQueue里面的,而PhantomReference则是先把自己放入ReferenceQueue之后在回收垃圾。所以WeakReference可以有ReferenceQueue,也可以没有。而PhantomReference则必须有。

        这里特别注意,PhantomReference与WeakReference在使用上其实是截然不同的。对于PhantomReference的实例化方式:1.如果使用PhantomReference reference=new PhantomReference(new Object(),ReferenceQueue)那么使用System.gc();是无法回收的,而WeakReference在这种实例化方式下却是可以的,实际上在实例化方式在PhantomReference中时完全释放不了的。2.如果使用Object obj=new Object();PhantomReference reference=new PhantomReference(obj,ReferenceQueue),那么在使用obj=null;System.gc();之后就可以回收。但是这里这里这种实例化方式不接受在当前方法调用obj=null;然后再下一个方法调用System.gc();这样是无法回收的,而WeakReference这样确实可以的(注意这个obj是全局变量还是局部变量在这里没有影响,也就是说,就算是全局变量也只能按这种方式实例化,回收。)。

        总的来说就是说,PhantomReference只接受一种实例化方式,一种回收方式,就是使用实例化Object obj=new Object();PhantomReference reference=new PhantomReference(obj,ReferenceQueue),使用obj=null;System.gc();回收。同时注意这个obj是全局变量还是局部变量在这里没有影响。


        这里有一点需要特别注意,在使用字符串实例化时,应该使用String str=new String(“”),这样才可以被回收。如果使用的是String str=“”;那么无论如何都不会回收。这点需要特别注意。同时 注意,一个方法的局部对象在方法被掉用完之后是会释放的。

        这里需要特别注意,对于类似class A{private B b=new B();};class B{};的情况,A的销毁会导致它里面的b成员变量的销毁,这是因为b成员变量的内存块是跟着A的内存块一起产生的,所以销毁是一起发生的。而class A{private B b;};class B{};这种情况则不一定,因为成员变量b的内存块并不会跟着A的内存块一起产生,b成员变量是在后面初始化的,所以A的销毁并不一定可以导致成员变量b的销毁。这点需要非常注意。


        以上这些就是引用的基本用法,实际使用中还涉及到间接引用,直线引用,分支引用,环引用等多种方式。同时涉及到GC的算法,例如copying collector,reference counting collector,compacting collector,tracing colector,generational collector,adaptive collector等算法,这些算法都是GC的内部算法,至于使用哪一种,看GC的使用场景。详细可以查找垃圾回收的算法。

        在使用例如Test test=new Test();而Test类有成员变量例如设置为共有的public String string=new String(“ddd”);然后有WeakReference<Test> reference=new WeakReference<Test>(test);我们使用test=null;System.gc();就可以回收,但是如果Test类里面的成员变量被引用了呢?这个时候有两种情况:1.使用例如String string=test.string();这个时候是可以回收的。因为如果回收了,而外部有有需要用到,那么还得在生成一个内存去给外部的引用,还不如就当作test内部的成员变量已经回收了,而实际上并没有回收,而是给了外部引用继续使用,这样就省去很多麻烦;或者说,这种情况操作的是对象,为了对象的正常使用所以出现这种情况。简单点就是说出现这种情况的可以回收,而被外部引用的成员变量不会被回收。2.例如使用String string=reference.get().string;这是不会释放,因为这里在内存区被引用了,而一个对象的释放就包括了他的全部的内存区的释放,而成员变量就在内存区里,或者说,一个对象的释放就包括了他的成员变量,方法等所有东西。3.使用reference.get().string去给另一个类进行实例化,或者使用它取坐位方法的传入参数,那么是可以释放的,例如Object obj=new Object(reference.get().string);或者getXX(reference.get().string);这时都是可以释放的。总的来说,就是操作内存区的成员变量,如果使用引用该内存区的对象的成员变量进行操作,那么是可以释放的,如果使用内存区的成员变量直接给某对象赋值那么是不可释放的,但是如果作为传参传给某个构造方法,或者方法,那么却又是可以释放的。

        这里对于reference.get().string的操作,其实可以归纳为:1.如果直接引用,那么系统自动判定为内存区被引用了,不可释放;2.如果是将reference.get().string作为构造方法或者方法的参数进行传递,系统就会在GC时认为传参的成员变量的内存已经释放了,而最后就释放整个内存区。这样做是最优化的,因为还有用的内存块忽略过去,就不用在整个内存区GC之后,在传建一个内存块给引用的对象使用。

        同时注意无论是使用reference.get().string,还是使用reference.get()进行强引用都是不可释放的,例如String string=reference.get().string;Test test=reference.get();都是不可回收的。所以上面的归纳可以使用这个规律简洁记忆。

        引用的应用中,是不提倡使用System.gc()对系统垃圾回收提醒的,因为大多数情况下系统会响应,而响应的话就会打乱垃圾回收的算法,这可能导致程序响应的问题,因为垃圾回收也是需要程序运行的,而回收垃圾的程序的运行意味着正在运行的程序的暂停。

        注意,在实际操作引用中,应该通过使用工具查看内存区的变化,来判定引用的内存区的操作产生的影响,而不是通过程序来看。我们可以通过使用eclipse自带的Heap,Allocation tracker等工具来查,在DDMS即可查看。(未完成)




(未完成)



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值