如何通过软引用和弱引用提升JVM内存使用性能

    初学者或初级程序员在面试时如果能证明自己具有【分析内存用量】和【内存调优】的能力,这相当有利,因为这是针对5年左右相关经验的高级程序员的要求。

   而对于高级程序员来说,如果能在面试时让面试官感觉你确实做过内存调优的工作,那么面试官很有可能不问Java Core部分的其它问题了,毕竟虚拟机调优是Java Core部分非常资深的知识点。

   在Java对象里,有强软弱虚四种引用,它们都和垃圾回收流程密切相关

   在项目里,可以通过合理的使用【不同类型的引用】来优化代码的内存使用性能。

通过new得到的内存空间的引用叫强引用。

  比如有String a = newString(“123”);其中的a就是一个强引用,它指向了一块内容是123的堆空间。

  平时我们用的最多的引用就是强引用,以至于很多人还不知道其他类型引用的存在,下面我们来说下弱软虚这三种平时不常见(但在关键时刻不可替代)的用途。    

一、软引用和弱引用的用法

   软引用(Soft Reference)的含义是:如果一个对象只具有软引用,而当前虚拟机堆内存空间足够,那么垃圾回收器就不会回收它,反之就会回收这些软引用指向的对象。

   弱引用(Weak Reference)与软引用的区别在于,垃圾回收器一旦发现某块内存上只有弱引用(一定请注意只有弱引用,没强引用),不管当前内存空间是否足够,那么都会回收这块内存。

  通过下面的ReferenceDemo.java,我们来看下软引用和弱引用的用法,并对比一下它们的差别。

public class ReferenceDemo {

    public static void main(String [] args){
        //强引用---在堆空间里分配一块空间(假设首地址是1000),在其中写入String类型的abc,并用str这个强引用指向这块空间
        String str = new String("adc");
        //软引用--用softRef这个软引用指向1000号内存,这时1000号内存上有一个强引用str,一个软引用softRef
        SoftReference<String> softRef = new SoftReference<String>(str);
        str = null;  //去掉强引用--把1000号内存上的强引用str撤去,此时该块内存上就只有一个软引用softRef
        System.gc(); //垃圾回收器进行回收---通过System.gc(),启动垃圾回收动作
        //通过softRef.get()输出软引用所指向的值,此时1000号内存上没有强引用,只有一个软引用
        //但由于此时内存空间足够,所以1000号内存上虽然只有一个软引用,但是gc垃圾回收不会回收1000号的内存,所以这里输出结果是abc
        System.out.println(softRef.get());
        //强引用--在堆空间分配一块空间(假设首地址是2000),在其中写入String类型的123,并用abc这个强引用指向这块空间
        String abc = new String("123");
        //弱引用--用weakRef这个弱引用指向2000号内存,这时2000号内存上有一个强引用abc,一个弱引用weakRef
        WeakReference<String> weakRef = new WeakReference<String>(abc);
        abc = null;  //去掉强引用--把2000号内存上的强引用abc撤去,此时该块内存上就只有一个弱引用weakRef
        System.gc(); //垃圾回收器进行回收--通过System.gc(),启动垃圾回收动作
        System.out.println(weakRef.get());

          //强引用
          String key = new String("456a");
          //虚引用
          ReferenceQueue<String> queue = new ReferenceQueue<String>();
          PhantomReference<String> phantomRef = new PhantomReference<String>(key,queue);
          key = null;
          System.gc();
          System.out.println(phantomRef.get());    }
}

输出结果:

  adc
  null

  null

定义了SoftReference<String>类型的软引用softRef,用来指向通过new创建的空间

定义了WeakReference<String>类型的弱引用weakRef ,用来指向通过new创建的空间

定义了PhantomReference<String>类型的虚引用phantomRef,用来指向通过new创建的空间

接下来我们通过下表来观察下具体针对内存空间的操作:

二、软引用的使用场景

           比如在一个博客管理系统里,为了提升访问性能,在用户在点击博文时,如果这篇博文没有缓存到内存中,则需要做缓存动作,这样其它用户在点击同样这篇文章时,就能直接从内存里装载,而不用走数据库,这样能降低响应时间。

          我们可以通过数据库级别的缓存做到这点,这里也可以通过软引用来实现,具体的实现步骤如下:

            1、可以通过定义Content类来封装博文的内容,其中可以包括文章ID、文章内容、作者、发表时间和引用图片等相关信息。

            2、可以定义一个类型为HashMap<String,SoftReference<Content>>的对象类保存缓存内容,其中键是String类型,表示文章ID,值是指向Content的软引用。

            3、当用户点击某个ID的文章时,根据ID到第二步定义的HashMap里去找,如果找到,而且所对应的SoftReference<Content>值内容不是null,则直接从这里拿数据并做展示动作,这样不用走数据库,可以提升性能。

            4、如果用户点击的某个文章的ID在HashMap里找不到,或者虽然找到,但对应的值内容是空,那么就从数据库去找,找到后显示这个文章,同时再把它插入到HashMap里,这里请注意,显示后需要撤销掉这个Content类型对象上的强引用,保证它上面只有一个软引用。

来分析下用软引用有什么好处?

   假设我们用1个G的空间缓存了10000篇文章,这10000篇文章所占的内存空间上只有软引用。如果内存空间足够,那么我们可以通过缓存来提升性能,但万一内存空间不够,我们可以依次释放这10000篇文章所占的1G内存,释放后不会影响业务流程,最多就是降低些性能。

  对比一下,如果我们这里不用软应用,而是用强引用来缓存,由于不知道文章何时将被点击,我们还无法得知什么时候可以撤销这些文章对象上的强引用,或者即使我们引入了一套缓存淘汰流程,但这就是额外的工作了,这就没刚才使用“软引用“那样方便了。

三、通过WeakHashMap来了解弱引用的使用场景

   WeakHashMap和HashMap很相似,可以储存键值对类型的对象,但我们可以从它的名字上看出,其中的引用是弱引用。

   通过下面的WeakHashMapDemo.java,我们来看下它的用法。

   

   通过下表,我们来详细说明关键代码的含义:

   

    根据上文和这里的描述,我们知道如果当一个对象上只有弱引用时,这个对象会在下次垃圾回收时被回收,下面我们给出一个弱引用的使用场景。

    比如在某个电商网站项目里,我们会用Coupan这个类来保存优惠券信息,在其中我们可以定义优惠券的打折程度,有效日期和所作用的商品范围等信息。当我们从数据库里得到所有的优惠券信息后,会用一个List<Coupan>类型的coupanList对象来存储所有优惠券。

    而且,我们想要用一种数据结构来保存一个优惠券对象以及它所关联的所有用户,这时我们可以用WeakHashMap<Coupan, List<WeakReference<User>>>类型的weakCoupanHM对象。其中它的键是Coupan类型,值是指向List<User>用户列表的弱引用。

   大家可以想象下,如果有100个优惠券,那么它们会存储于List<Coupan>类型的coupanList,同时,WeakHashMap<Coupan, List<WeakReference<User>>>类型的weakCoupanHM对象会以键的形式存储这100个优惠券。而且,如果有1万个用户,那么我们可以用List<User>类型的userList对象来保存它们,假设coupan1这张优惠券对应着100个用户,那么我们一定会通过如下的代码存入这种键值对关系,weakCoupanHM.put(coupan1,weakUserList);,其中weakUserList里以弱引用的方式保存coupan1所对应的100个用户。

    这样的话,一旦当优惠券或用户发生变更,它们的对应关系就能自动地更新,具体表现如下:

1、当某个优惠券(假设对应于coupan2对象)失效时,我们可以从coupanList里去除该对象,coupan2上就没有强引用了,只有weakCoupanHM对该对象还有个弱引用,这样coupan2对象能在下次垃圾回收时被回收,从而weakCoupanHM里就看不到了。

2、假设某个优惠券coupan3用弱引用的方式指向于100个用户,当某个用户(假设user1)注销账号时,它会被从List<User>类型的userList对象中被移除。这时该对象上只有weakCoupanHM里的值(也就是List<WeakReference<User>>)这个弱引用,该对象同样能在下次垃圾回收时被回收,这样coupan3的关联用户就会自动地更新为99个。

如果不用弱引用,而是用常规的HashMap<Coupan,List<User>>来保存对应关系的话,那么一旦出现优惠券或用户的变更的话,那么我们就不得不手动地更新这个表示对应关系的HashMap对象了,这样,代码就会变得复杂,而且我们很有可能因疏忽而忘记在某个位置添加更新代码。相比之下,弱引用给我们带来的“自动更新“就能给我们带来很大的便利。

四、不能投机取巧,但面试确实有技巧

笔者写本文的意思,不是让大家投机取巧,事实上,如果大家只知道这些知识,而不知道其他虚拟机(或Java Core)相关的知识点,面试通过的可能性很低。

但话说回来,如果大家在平时开发时积累了很多经验,但不会总结,在面试时也无法很好地展示各种能力,这样也是非常可惜的。

根据本人在培训学校的经验,首先通过可能掌握各种Java技能,在这个基础上再讲述上述软引用和弱引用的技能,这些候选人得到的反馈是,至少在Java Core方面比较精通。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值