你不必使用弱引用以避免内存泄漏

本文探讨了弱引用在Android开发中的作用及其使用场景。通过对比不同类型的引用,文章详细解释了弱引用如何帮助解决内存泄漏问题,并提供了具体的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文翻译自https://medium.com/google-developer-experts/weakreference-in-android-dd1e66b9be9d#.7gyh5qy6l

我的一位同事最近提到,他们看到了一个演讲,说:“如果你是一个Android开发者,你不使用使用弱引用,将会有问题。”
我个人认为,这不仅是一个错误的论点,而且完全是一个误导。WeakReference应该是修复内存泄漏的最后手段。
下面是谷歌开发专家Enrique López Mañas发布的文章:“
Finally understanding how references work in Android and Java
(英文不好,弱弱的依赖翻译工具翻译一下,正文如下)
几个星期前,我参加了mobiconf,这是一个最好的移动开发者的会议之一,我有幸在波兰参加。在他不拘一格的演讲“The best (good) practices”期间,我的朋友和同事Jorge Barroso提出了让我说说自己的想法关于:”如果你是一个Android开发者,你不使用使用弱引用,你将会有问题。”
就一个好时机的例子,几个月前,我确实发表了我的最后一本书,和Diego Grancini一起合著的—–“Android High Performance(安卓高性能)“,其中最有激情的章节之一是一个谈论在安卓系统的内存管理。在这一章中,我们讨论了在移动设备中的内存如何工作,内存泄漏是如何发生的,我们可以应用哪些技术来避免它们和为什么避免内存泄漏如此重要。自从我开始开发android,我总是观察到一种倾向,人们不知不觉的避免或把内存泄漏和内存管理相关的处理的优先级低于其它一切。如果功能标准得到满足,为什么要自增烦恼呢?我们总是在匆忙开发新的功能,我们宁愿在我们的下一个Sprint demo 演示视觉可见的东西,而不是关心没有人会在第一眼看到的东西。
一个很好的论点,这不可避免地导致收购技术债务。我甚至会补充说,技术债务也有一定的影响,在现实世界中我们不能用单位测试衡量:失望,在同开发商之间的摩擦,低质量的软件运和损失的动机。这种影响是很难衡量的原因是因为通常他们发生在一个长的周期内。它的发生有点像政治家:如果我只会任期8年,为什么我要担心在12后发生了什么事?不同的是在软件开发中一切动作速度更快一些。
要完全写出在软件开发中采用适当的和正确的思维方式,可能需要写很多,而且已经有许多你可以探索书籍和文章了。然而,简要阐述的内存引用的不同类型,它们的意义是什么,他们如何能被应用在Android将是一个简短的任务,这就是我想做的文章。

First of all: what is a reference in Java?

A reference is the direction of an object that is annotated, so you
can access it.

Java has by default 4 types of references: strong, soft, weak and phantom.

有些人认为,只有两种类型的引用,强和弱,弱引用可以呈现2个程度的弱引用。我们倾向于把生活中的一切都与一个植物学家的分类的毅力。无论它对你的工作更好,但首先你需要了解他们。然后你可以找到你自己的分类。

What does each type of reference mean?(每种引用类型意味着什么?)
Strong reference: 强引用是java的普通引用。任何时候我们创建一个新的对象,一个强引用默认创建。例如,当我们这样做:

MyObject object = new MyObject();

一个新的对象是MyObject被创造,则一个它的强引用被存储在对象中。此对象是 strongly reachable ,意味着它可以通过一个强引用到达,这将防止垃圾回收器销毁它。但是现在我们来看一个反对我们观点的例子:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        new MyAsyncTask().execute();
    }

    private class MyAsyncTask extends AsyncTask {
        @Override
        protected Object doInBackground(Object[] params) {
            return doSomeStuff();
        }
        private Object doSomeStuff() {
            //do something to get result
            return new MyObject();
        } 
    }
}

AsyncTask将在oncreate()方法创建和执行。但在这里,我们有一个问题:内部类需要访问外部类在其整个生命周期。当activity被销毁时会发生什么?AsyncTask拥有activity的引用,以至于activity不能被GC回收。这就是我们所说的内存泄漏。

实际上,内存泄漏不仅发生在activity本身被销毁时,而且发生在由于配置改变或内存不足而被系统强制销毁时,等等。如果AsyncTask是复杂的(即拥有activity中view的引用等)甚至可能导致死机,因为view引用为null。

那么如何才能防止这个问题再次发生呢?我们来看一下其它类型的引用:
WeakReference: 弱引用是一个没有足够强大保持对象一直存在内存中的引用。如果我们不确定是否对一个对象使用强引用,这时就出现了弱引用,该对象将被垃圾回收器收集。为了便于理解,最好是抛开理论用一个实际的例子,用WeakReference去改写我们以前的代码来避免内存泄漏:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new MyAsyncTask(this).execute();
    }
    private static class MyAsyncTask extends AsyncTask {
        private WeakReference<MainActivity> mainActivity;    

        public MyAsyncTask(MainActivity mainActivity) {   
            this.mainActivity = new WeakReference<>(mainActivity);            
        }
        @Override
        protected Object doInBackground(Object[] params) {
            return doSomeStuff();
        }
        private Object doSomeStuff() {
            //do something to get result
            return new Object();
        }
        @Override
        protected void onPostExecute(Object object) {
            super.onPostExecute(object);
            if (mainActivity.get() != null){
                //adapt contents
            }
        }
    }
}

请注意一个主要的区别:内部类的activity的引用现在如下:

private WeakReference<MainActivity> mainActivity;

这样会发生什么?当activity不再存在,因为它是通过一个WeakReference的方式,它可以被垃圾回收器回收。因此,没有内存泄漏会发生。

现在,我希望你对使用弱引用有更进一步的了解了,你会发现一个有用的类WeakHashMap。它如同一个HashMap,除了钥匙(key,不是values)是指通过使用弱引用。这使他们非常有用,如高速缓存。

我们之前提到了更多的引用。让我们看看它们都用在哪里,有什么优点:
SoftReference:我们可以把软引用看作为一个稍强一点的WeakReference。而WeakReference会立即被收集,软引用会请求GC尽可能不回收它除非没有其他的选择。垃圾收集器算法是个很令人兴奋的东西,你可以潜心研究它几个小时不觉得累。但基本上,垃圾回收器会说,“我将一直收回WeakReference。如果对象是一个软引用,我会根据系统条件决定去怎么处理”。这使得软引用在缓存实现时非常有用:只要内存足够,我们不必手动删除对象。为了了解软引用,下面是另一个blog中的例子
《《《《java中的每个对象都有对其他对象的引用。只要一个对象引用了它,则该对象将不会被回收。当应用程序内存溢出而且GC无法删除那些没有真正引用的对象来释放内存,只有softrefences将被删除。所以, 基本 ,只有软引用它将 项目保持 在内存尽可能长的时间,直到没有其他的方法来释放内存。
创建一个软引用一个对象,你可以使用下面的方式:

SoftReference<String> ref = new SoftReference<String>("Hello world");

检索数据:

String value = ref.get();

if (value == null) {
  // The reference is cleaned up by the Garbage Collector.
  // Initialize the object again.
}

// Use the value (from the SoftReference or from re-initializing.
.......

SoftReferenceCache
我们如何创建一个缓存使用softreferences?我们将做一个HashMap和java泛型。
首先我们从一个叫 softreferencecache会接收两个泛型参数的类开始。一个key一个value,在构造函数中我们将创建HashMap。

public class SoftReferenceCache<K, V> {
  private final HashMap<K, SoftReference<V>> mCache;

  public SoftReferenceCache() {
    mCache = new HashMap<K, SoftReference<V>>();
  }
}

向缓存中添加新的键值对的方法

public void put(K key, V value) {
  mCache.put(key, new SoftReference<V>(value));
}

根据key 取value的方法

public V get(K key) {
  V value = null;

  SoftReference<V> reference = mCache.get(key);

  if (reference != null) {
    value = reference.get();
  }

  return value;
}

使用这个缓存类如下

SoftReferenceCache<Integer, Person> mPersonCache = new SoftReferenceCache<Integer, Person>();

mPersonCache.put(0, new Person("Peter");
mPersonCache.put(1, new Person("Jan");
mPersonCahce.put(2, new Person("Kees");

Person p = (Person)mPersonCache.get(1); // This will retrieve the Person object of Jan.
/**
 * SoftRefenceCache
 * @param <K> The type of the key's.
 * @param <V> The type of the value's.
 */
public class SoftReferenceCache<K, V> {
  private final HashMap<K, SoftReference<V>> mCache;

  public SoftReferenceCache() {
    mCache = new HashMap<K, SoftReference<V>>();
  }

  /**
   * Put a new item in the cache. This item can be gone after a GC run.
   * 
   * @param key
   *            The key of the value.
   * @param value
   *            The value to store.
   */
  public void put(K key, V value) {
    mCache.put(key, new SoftReference<V>(value));
  }

  /**
   * Retrieve a value from the cache (if available).
   * 
   * @param key
   *            The key to look for.
   * @return The value if it's found. Return null if the key-value pair is not
   *         stored yet or the GC has removed the value from memory.
   */
  public V get(K key) {
    V value = null;

    SoftReference<V> reference = mCache.get(key);

    if (reference != null) {
      value = reference.get();
    }

    return value;
  }
}

》》》》》》》》》》》》》》》》》》》》》》》》》》》

回到正题,接下来是

PhantomReference:哎,phantomreferences!我想我可以用手指头就可以数的过来他们在生产环境中使用的次数。一个对象如果只通过phantomreference引用,那么不管何时垃圾回收器都可以回收它。那我们为什么还要用它呢?phantomreference可准确检测一个对象是否被从内存中移除。


大神 Enrique López Mañas的文章大概这些,当然我省去了一些,翻译的不好之处请见谅。

这是非常好的文章,用例子讲解了java中引用是如何工作的。文章没有说,我们必须使用WeakReference但也没有给其它选择。我觉得我必须给出其它选择来证明不是必须使用WeakReference。

我相信使用WeakReference并不是在每个地方都是最佳选择。使用一个WeakReference修复内存泄漏表明缺乏建模或建筑。虽然在文章中给出的例子修复了潜在的内存泄漏,但还有其他方式。我将给出几个示例实现,以避免当您有一个长时间运行的后台任务时内存泄漏。

一个简单的例子来避免内存泄漏的AsyncTask会是这样的:

以下是我的 Activity:

public class MainActivity extends Activity {
  private MyAsyncTask task;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    task = new MyAsyncTask();
    task.setListener(createListener());
    task.execute();
  }
  @Override
  protected void onDestroy() {
    task.setListener(null);
    super.onDestroy();
  }
  private MyAsyncTask.Listener createListener() {
    return new MyAsyncTask.Listener() {
      @Override
      public void onSuccess(Object object) {
        // adapt contents
      }
    };
  }
}

以下是我的 AsyncTask:

class MyAsyncTask extends AsyncTask {
  private Listener listener;
  @Override
  protected Object doInBackground(Object[] params) {
    return doSomeStuff();
  }
  private Object doSomeStuff() {
    //do something to get result
    return new Object();
  }
  @Override
  protected void onPostExecute(Object object) {
    if (listener != null) {
      listener.onSuccess(object);
    }
  }
  public void setListener(Listener listener) {
    this.listener = listener;
  }
  interface Listener {
    void onSuccess(Object object);
  }
}

就是以接口的方式来回调,这种实现是非常基本的,但我认为这是不够的,以展示另一种解决方案。

以下是一个基于rxjava 非常简单的实现,当我们没有WeakReference时:

public class MainActivity extends Activity {

  private Subscription subscription;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    subscription = Observable
        .fromCallable(new Callable<Object>() {
          @Override
          public Object call() throws Exception {
            return doSomeStuff();
          }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Action1<Object>() {
          @Override
          public void call(Object o) {
            // adapt contents
          }
        });
  }

  private Object doSomeStuff() {
    //do something to get result
    return new Object();
  }

  @Override
  protected void onDestroy() {
    subscription.unsubscribe();
    super.onDestroy();
  }

}

请注意,如果我们不取消订阅,我们可能仍然有内存泄漏。

最后我想给几个来自Novoda的工程实例。他们是伟大的学习资源。你能猜到的,他们没有任何WeakReference :)

第一个实例

第二个实例

我相信经验法则(对我来说)当使用内部类时要使用静态内部类。特别是如果他们在后台做长时间运行的话。可以的话,我们尽量把这些类变成了一个完整的类。

本文翻译自https://medium.com/google-developer-experts/weakreference-in-android-dd1e66b9be9d#.7gyh5qy6l

<think>我们正在讨论Python中的弱引用机制,特别是如何利用它来避免内存泄漏。根据引用[3][4][5],弱引用是一种不会增加对象引用计数的特殊引用,因此它不会阻止对象被垃圾回收器回收。这在处理循环引用时特别有用,因为循环引用可能导致引用计数无法归零,从而造成内存泄漏。 接下来,我将详细解释弱引用的原理、应用场景以及如何通过它避免内存泄漏。 ### 1. 弱引用的原理 在Python中,对象的生命周期通常由引用计数管理。当一个对象的引用计数降为0时,它会被立即回收。但是,如果存在循环引用(例如,对象A引用对象B,同时对象B引用对象A),并且这些对象没有被其他对象引用,它们本应被回收,但由于循环引用导致它们的引用计数永远不为0,这就造成了内存泄漏。 为了解决循环引用问题,Python引入了垃圾回收器(GC),它可以检测并回收循环引用的对象。但是,垃圾回收器是周期性地运行的,而且它的运行会带来一定的性能开销。此外,在某些情况下,循环引用可能包含需要及时释放的资源(如文件句柄、网络连接等),我们不能依赖垃圾回收器来及时释放这些资源。 弱引用提供了一种解决方案:它允许我们引用一个对象,但不增加其引用计数。因此,即使存在弱引用,只要对象的其他引用都被删除,该对象就可以被回收。弱引用通过`weakref`模块实现,主要提供以下工具: - `weakref.ref`:创建一个弱引用对象,通过调用该对象可以获取原对象(如果还存在)。 - `WeakValueDictionary`:一个字典类,其值保存的是弱引用。当值对象被回收时,对应的键值对会自动删除。 - `WeakKeyDictionary`:类似,但键是弱引用。 - `WeakSet`:一个集合类,保存的是弱引用。 ### 2. 如何通过弱引用避免内存泄漏 弱引用常用于以下场景以避免内存泄漏: #### 场景1:打破循环引用 假设有两个对象A和B,它们相互引用(形成循环引用)。如果我们希望这两个对象在不再被外部使用时能被及时回收,可以将其中一个引用改为弱引用。例如,将对象B对对象A的引用改为弱引用,这样当外部对A和B的引用都删除时,A的引用计数变为0,B的引用计数变为1(因为A引用了B)。然后A被回收,同时A对B的引用也被删除,导致B的引用计数变为0,B也被回收。 示例代码: ```python import weakref class Node: def __init__(self, name): self.name = name self.parent = None self.children = [] def add_child(self, child): self.children.append(child) # 使用弱引用来设置父节点,避免循环引用 child.parent = weakref.ref(self) # 创建节点 root = Node("root") child = Node("child") root.add_child(child) # 此时,child的parent是一个指向root的弱引用 print(child.parent()) # 输出: <__main__.Node object at ...> (即root对象) # 删除root的强引用 del root # 此时,root对象由于没有强引用,会被回收 # 因此,child.parent()将返回None import gc gc.collect() # 强制进行垃圾回收 print(child.parent()) # 输出: None ``` #### 场景2:缓存对象 当我们需要缓存一些创建成本高但非必须长期存在的对象时,可以使用弱引用来构建缓存。这样,当对象在其他地方不再被使用时,它们可以被自动回收,从而避免内存泄漏。 示例:使用`WeakValueDictionary`实现缓存 ```python import weakref class Image: def __init__(self, name): self.name = name # 假设这里加载了很大的图片数据 print(f"加载图片: {name}") # 缓存字典,保存图片的弱引用 image_cache = weakref.WeakValueDictionary() def get_image(name): if name in image_cache: # 如果缓存中有,返回缓存的图片 return image_cache[name] else: # 否则创建新图片,并加入缓存 image = Image(name) image_cache[name] = image return image # 使用 img1 = get_image("background.png") # 第一次加载 img2 = get_image("background.png") # 从缓存中获取 # 删除所有对图片的强引用 del img1, img2 # 强制垃圾回收,此时缓存中的图片对象会被回收 gc.collect() print(len(image_cache)) # 输出: 0 ``` ### 3. 注意事项 - **弱引用的不可预测性**:弱引用指向的对象可能在任何时候被回收,因此在使用弱引用时,必须检查对象是否还存在(通过调用弱引用对象,如果返回None则已被回收)。 - **不可哈希对象**:不是所有对象都可以被弱引用。例如,列表、字典等内置可变类型不能被直接弱引用,但可以通过子类化或使用`weakref.proxy`来间接实现(但要注意,`weakref.proxy`只能用于某些特定类型)。 - **资源管理**:弱引用不能替代显式的资源释放。对于需要及时释放的资源(如文件句柄),最好使用上下文管理器(`with`语句)或显式调用关闭方法。 ### 4. 总结 弱引用通过不增加对象的引用计数,允许对象在失去所有强引用时被垃圾回收器回收,从而有效避免循环引用导致的内存泄漏。主要应用场景包括: 1. 打破循环引用(如树形结构、双向链表等)。 2. 构建缓存系统(如`WeakValueDictionary`、`WeakSet`等)。 合理使用弱引用可以提高程序的健壮性和效率,但需要注意其不可预测性,并确保在必要时检查弱引用对象是否还存在[^3][^4][^5]。 ### 相关问题 1. 弱引用在垃圾回收机制中扮演什么角色? 2. 如何用弱引用实现一个对象缓存池? 3. 弱引用和普通引用在性能上有何差异? 4. 哪些对象不能被弱引用?如何处理? 5. 弱引用与终结器(finalizer)有何关联?</think>### Python弱引用避免内存泄漏的原理与应用 在Python中,弱引用(weak reference)是解决内存泄漏问题的核心机制之一,尤其擅长处理**循环引用**场景。其原理和应用如下: #### 一、避免内存泄漏的原理 1. **引用计数机制缺陷** Python使用引用计数管理内存,当对象引用计数归零时自动回收。但**循环引用**会导致计数永不归零: ```python class Node: def __init__(self): self.ref = None # 相互引用 a = Node() b = Node() a.ref = b # a引用b b.ref = a # b引用a → 循环引用 ``` 此时即使删除`a`和`b`,引用计数仍为1,内存泄漏发生[^1]。 2. **弱引用的核心机制** - 弱引用通过`weakref`模块创建,**不增加目标对象的引用计数** - 当目标对象仅被弱引用时,垃圾回收器可正常回收它 - 弱引用对象会返回`None`或触发回调(若目标被回收) ```python import weakref a = Node() b_ref = weakref.ref(a) # 创建弱引用 print(b_ref()) # 输出: <__main__.Node object at ...> del a print(b_ref()) # 输出: None → 对象已被回收 ``` 3. **垃圾回收协同工作** 弱引用与Python的`gc`模块协同: - 标记-清除算法检测循环引用 - 弱引用对象被视为"可回收"节点 - 回收后自动将弱引用设为`None`[^3][^5] #### 二、关键应用场景 1. **打破循环引用** ```python class TreeNode: def __init__(self, name): self.name = name self.parent = None self.children = [] def add_child(self, child): self.children.append(child) # 使用弱引用避免父子循环引用 child.parent = weakref.ref(self) ``` 2. **缓存系统实现** 使用`WeakValueDictionary`构建自动清理的缓存: ```python from weakref import WeakValueDictionary class ImageCache: def __init__(self): self._cache = WeakValueDictionary() def load_image(self, path): if image := self._cache.get(path): return image # 加载大内存图片 image = load_large_image(path) self._cache[path] = image return image ``` 当图片不再被外部引用时,缓存自动清除[^3][^4]。 3. **观察者模式优化** 避免观察者持有被观察对象的强引用: ```python class Observer: def __init__(self, subject): self.subject_ref = weakref.ref(subject) def notify(self): if subject := self.subject_ref(): subject.update() ``` #### 三、使用注意事项 1. **不可哈希对象的限制** 列表、字典等可变类型不能直接弱引用,需封装: ```python weakref.proxy(my_list) # 使用代理 ``` 2. **生命周期管理** 弱引用对象可能随时失效,使用前必须检查: ```python if obj := weak_ref(): obj.method() ``` 3. **回调函数应用** 注册回收通知: ```python def on_finalize(ref): print(f"对象 {ref} 已被回收") obj = SomeObject() weakref.finalize(obj, on_finalize) # 回收时触发 ``` #### 四、性能对比 | **引用类型** | 引用计数影响 | 内存泄漏风险 | 适用场景 | |-------------|------------|------------|---------| | **强引用** | 增加计数 | 高(循环引用) | 对象所有权明确 | | **弱引用** | 不增加计数 | 低 | 缓存/观察者/循环结构 | > **最佳实践**:当对象需要被引用但不应阻止其回收时,优先使用弱引用。对于文件句柄、网络连接等资源,仍需显式关闭[^4]。 ### 相关问题 1. 如何检测Python程序中的内存泄漏?[^1][^2] 2. `WeakValueDictionary`和`WeakKeyDictionary`有何区别?[^3] 3. 弱引用在异步编程中如何应用?[^2] 4. Python垃圾回收机制如何与弱引用协同工作?[^5] 5. 哪些场景不适合使用弱引用?[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值