Android中的对象

163 阅读6分钟

Android对象

面向对象编程中,对象无处不在,无时不在

对象引用

Android中对象引用按级别从高到低有强、软、弱和虚引用

  • 强引用
    • 使用最多的引用,若一个对象使用强引用,垃圾回收器不会回收该对象。当空间不足时,会抛出OutOfMemoryError错误,终止程序
  • 软引用
    • 若一个对象使用软引用,内存充足时,垃圾回收器不回收该对象,内存不足时,才会回收
    • 只有未回收时才可使用,常用来实现内存敏感的高速缓存(例软引用与引用队列一起使用,回收后加入到引用队列中标记)
  • 弱引用
    • 比软引用具有更短的生命周期,垃圾回收器扫描到弱引用区域时不管内存情况,直接回收
    • 垃圾回收器是一个低优先级的线程,不一定会很快扫描到弱引用对象
    • 可与引用队列一起使用,回收后加入到引用队列中标记
  • 虚引用
    • 形同虚设,与其他引用方式不同,不会决定对象的生命周期
    • 若一个对象是虚引用,则和没有任何引用一样,随时都可能被垃圾回收器回收
    • 主要用来跟踪对象被垃圾回收器回收的动作
    • 必须与引用队列一起使用,回收虚引用对象时,会在回收前将该对象加入至与之关联的引用队列中
    • 程序可以通过判断引用队列是否已加入虚引用对象,来了解该对象是否将被垃圾回收,程序发现虚对象被加入,可在回收前做必要操作
val any = Any()
val queue = ReferenceQueue<Any>()
val pr = PhantomReference(any, queue)

对象回收

垃圾回收器判断对象是否可被回收,常用的算法有两种:引用计数器算法可达性分析算法

引用计数器算法

对象中添加引用计数器。引用增加时,计数器+1,引用失效时,计数器-1,只要计数器为0就代表对象可被回收

  • 需额外内存来计数,并且运行期间需维护计数器,带来额外开销
  • 无法解决循环引用问题

可达性分析算法

很多场景下,一个对象并不是从根节点直接引用的,而是被一个或多个其他对象引用,从而形成了一个以根节点为顶的树状结构

可达性分析算法通过从根节点开始,通过引用关系,沿着引用链搜寻,如果某个根节点没有任何引用链相连,就不可达,可被回收

  • 可达对象:双方存在直接或间接的引用关系
  • 根可达:对象到根节点对象存在直接或间接的引用关系
  • 根节点对象:确定当前不能被回收的对象(不能被回收的对象所依赖的对象也不能被回收),如下是可作为根节点对象
    • 全局对象
      • 方法区静态属性引用对象:Class对象很难回收,Class不回收,静态成员也不会回收,可作为根节点对象
      • 方法区常量池引用的对象:常量初始化后不再改变,可作为根节点对象
    • 执行上下文
      • 方法栈中栈桢本地变量表引用的对象
        • 方法执行时,会被打包成一个栈帧入栈执行,方法中局部变量会存放在栈帧的本地变量表中,只要方法还在运行,未出栈,则本地变量表中的对象还会被访问,不能回收,可作为根节点对象
      • JNI本地方法栈中引用的对象:同上,区别是一个是java方法栈引用变量,一个是native方法栈引用变量
      • 被同步锁持有的对象:被synchronized锁住对象不可回收,当有线程持有对象锁时,若GC回收了对象,锁就会失效,可作为根节点对象

对象使用

强引用是使用最多、最普遍的方式,此处不做介绍

软引用(构建敏感数据缓存)

当我们上网浏览网页或检索信息时有时可能会返回查看之前浏览的网页或检索信息。

从程序开发者的角度看实现这种功能有两种实现方式:

  • 查看过的信息保存在内存,贯穿程序的整个生命周期
    • 会造成大量的内存浪费
  • 查看后结束对象引用,使得垃圾回收器可以回收内存,再次访问时,重新查看
    • 对象可以回收不代表已经被回收,当未回收时也需要创建新对象,浪费内存 有没有一种办法可以重新获取那些尚未被回收的对象引用,减少不必要的访问,大大提高程序的运行速度呢-----那就是使用软引用
  • 软引用SoftReference的实例会保存一个对对象的软引用,该软引用不妨碍垃圾回收器对该对象的回收
    • 即未回收时软引用就相当于强引用,回收后获取对象为null
/**
 * 对象any存在两个引用路径
 *  1. 自身强引用
 *  2. 软引用
 */
var any: Any? = Any()
val soft = SoftReference(any)

// 手动置为null,当垃圾回收器进行回收时不会因为有软引用而保留对象
any = null

// 此时获取对象为null
soft.get()
  • 软引用SoftReference本身也是一个对象,使用过多也会入内存的问题
    • ReferenceQueue可用于清除失去软引用对象的SoftReference
/**
 * SoftReference除了具有保存软引用的特性外,还具有对象的一般特性,也需要回收
 *  当关联的可达对象被回收后,其也不具有任何价值,需在合适的时机清除,避免大量SoftReference带来的内存问题
 *
 * ReferenceQueue可解决SoftReference带来的问题,只需在创建软引用对象时加入该参数即可
 *  当SoftReference关联对象被垃圾回收器回收时,自身会被加入到ReferenceQueue
 */
var any: Any? = Any()
val queue = ReferenceQueue<Any?>()
val soft = SoftReference(any, queue)

/**
 * ReferenceQueue中存的是失去软引用对象的SoftReference对象
 * ReferenceQueue#poll获取对象不为null,说明该软对象可以回收
 */
var ref = queue.poll()
while (ref != null) {
    // 回收
    ...
    ref = queue.poll()
}
  • 使用软引用实现Bitmap高速缓存
/**
 * 软引用实现Bitmap高速缓存
 */
class SoftReferenceCache {
    private val queue = ReferenceQueue<Bitmap>()
    private val cache = mutableMapOf<String, SoftReference<Bitmap>>()

    fun put(key: String, bitmap: Bitmap) {
        val softReference = SoftReference(bitmap, queue)
        cache[key] = softReference
        checkClearReferences()
    }

    fun get(key: String): Bitmap? = cache[key]?.get()

    /**
     * 校验是否有需要回收的软对象
     */
    private fun checkClearReferences() {
        var reference = queue.poll()
        while (reference != null) {
            cache.entries.removeIf { it.value == reference }
            reference = queue.poll()
        }
    }
}