EventBus调register注册的“接收方法”会保存多久?--static、final、static final内存模型及思考

此文章依赖知识点

  1. Java内存模型
  2. Java类加载机制
  3. EventBus 使用
  4. EventBus 源码阅读

问题由来

  1. 学习EventBus过程中发现,粘性事件和普通事件都会被保存在一个final修饰的Map集合中。
  2. 由此就得出疑问:① 为什么不用static修饰?(按我的理解应该用static,因为static修饰这个集合就能一直活到App结束);② 为什么不是用static final进行修饰? ;③ 这个final集合能活多久?(太短的话,A、B两个Activity,A调用了register方法,B调用post方法发东西给A。如何保证post时,A不被回收?);④ 平常定义常量时,为什么要用static final?为什么叫编译期常量?

分析过程

首先是 static、final、static final的区别?

  1. 所有文章都传递了一个观点:static表达“唯一”,final表达“不变”

  2. 这就让我有个疑问了:

    1. 众所周知,static是类变量,类加载后会保存在方法区的.class文件中,因此static表达“唯一”,没问题;但是,final修饰的是常量,常量会保存在方法区的.class文件里的常量池中,因此也是类独有的。那么final也能表达“唯一”才对啊?

    常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等

    1. 解答:实际上final修饰基本数据类型,才算是常量,只有值会被放到常量池中,只有值可以算是“唯一”的,变量引用不能;而final修饰对象类型,不能算作是常量!final修饰的Map,是对象类型,可以看成是成员变量,应该会放在堆中;EventBus用final修饰Map,只是为了保证它不被指向其他内存地址罢了。
  3. 再来看看 问题④:为什么平常定义常量时,要用static final?为什么叫编译期常量?

    1. 如果只用static,那么①:它会在类加载的准备阶段,分配类变量的内存,并设置初始值,在类加载的初始化阶段(<clinit>)进行实际的赋值,这就说明,static变量会在编译期赋好值然后塞到.class对象中(这就是编译期的意思)。②:但是它可以在运行期间被更改,而且因为是类所有,一个对象实例更改后,其他对象实例拿到的都是更改后的值,则它不是一个常量。
      比如:

      class B{
      public static int a = 5;
      }
      
      B b1 = new B();
      B b2 = new B();
      b1.a = 10;
      //b2.a 的值为10
      

      总结:只用static可以保证编译期赋值,不能保证运行期不被更改!

    2. 如果只用final,那么 ①:它会在编译期(<clinit>)时,把值存到常量池中(比如final int d = 20,20会存到常量池),运行期调用<init>后才赋值,每个对象实例拿到的可能都不是同一个引用。②:但是运行期每个引用都不会再被更改。
      比如:

      class A{
      public final int i = new Random(30).nextInt(21);
      }
      
      A a1 = new A();
      A a2 = new A();
      

      这里a1.i的值跟a2.i的值不一样!说明运行期才赋值。
      总结:只用final可以保证<init>赋值后不会被更改

    3. 用static final,先得出结论:编译期就被赋值,而且永远不会被更改!
      比如:
      public static final int f = 20;
      在类加载的准备阶段,把 f 初始化成0,然后初始化阶段<clinit>赋值为20,接着把20保存到常量池。
      这样就能保证 编译期就生成了不会被更改的变量!

回到问题:①为什么不用static修饰?② 为什么不是用static final进行修饰? ③ 这个final集合能活多久?;

  1. 首先思考,final修饰对象引用的变量,可以算作是成员变量,那么会被保存到堆中,而堆中的东西会在对象被回收时GC
    比如:

    class EventBus{
    private final Map map= new HashMap;
    }
    

    这个 map 会在回收 EventBus 时被回收,而EventBus什么时候被回收呢?
    假如有Activity A 和 B,A中调用了 new EventBus().register(xxx),把东西保存到map中, 接着跳转到B,此时GC,则A被回收了,A中相应的 EventBus实例 也被回收,导致map也被回收。那么在B中无论如何post,都找不到保存在map中的A了。
    总结:只用final无法保证Map集合在EventBus.post调用时不会被回收

  2. 此时觉得很奇怪,为什么 EventBus 只是用final修饰了Map呢? 我用static修饰,才会正确的执行啊:
    比如:

    class EventBus{
    private static Map map= new HashMap;
    }
    

    此时这个map会被保存在方法区的.class文件中,是类共享的。
    何时回收呢?在类卸载的时候才会回收。
    何时卸载呢?没有任何引用的时候(① 加载它的类加载器被回收 ② ……③ ……)。
    加载它的类加载器什么时候被回收呢?在App结束后。
    因此,map能跟App活一样长。
    而再加上final能保证map不会被指向其他map
    所以,光用final修饰时肯定不对的,一定要用static!!

那难道是 EventBus 的编写者错了?
  1. 忽略了一点,我们使用EventBus时,都是EventBus.getDefault().register(this)这么用的,而 getDefault() 是一个单例,单例中的对象实例是用static修饰的,因此能保证它能活的跟App一样长,则它内部的Map也不会被回收。

经过这些思考,我们就可以很安心的使用EventBus,而不用担心post时,register的方法会不会突然被GC回收了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值