Android:静态字段和内存泄漏

Posted

技术标签:

【中文标题】Android:静态字段和内存泄漏【英文标题】:Android : Static Fields and Memory Leaks 【发布时间】:2012-08-08 03:10:07 【问题描述】:

我一直在研究在创建视图时防止上下文/活动内存泄漏的最佳实践,但对于类中的静态字段,我似乎无法找到明确的答案。

假设我有一个这种形式的代码:

public class MyOuterClass extends Activity
   private MyInnerClass;
   MyInnerClass = (MyInnerClass) findViewById(<XML call here>);
   MyInnerClass.myXInt = 3;

   // onCreate(), onResume(), etc.

   public static class MyInnerClass extends SurfaceView implements Runnable
      // Safe variables?
      private static int myXInt, myYInt;
      private static boolean myBoolean;
      // Potentially safe?
      private static Canvas myCanvas;
      // Definitely bad.
      private static Context myContext;

      public MyInnerClass(Context context)
         myContext = context;        // This is bad.
      
   

我对 JVM 实际上认为 MyInnerClass 的 ClassLoader 是什么感到有些困惑。从技术上讲,因为它是一个 SurfaceView 对象,所以一旦应用程序实例化 MyInnerClass 一次(这发生在 View 第一次膨胀时),静态变量似乎应该始终存在,然后保持在那里直到应用程序本身终止。如果是这样,是什么阻止了位图和画布对象也保持打开状态并填满堆?

我见过的唯一一个反复重复的声明是,你不能像我在构造函数中显示的那样泄漏静态上下文,但它永远不会超出这个范围。这真的是你唯一不能做的事吗?

【问题讨论】:

您的Canvas 等不必是static。这样它确实会永远留在堆中 如果是这种情况,那么是什么阻止了常量(即 - private static final int MY_CONSTANT)也保持任何扩展活动(及其上下文)打开的类? 【参考方案1】:

在 Java/android 中,static 变量或常量不会被垃圾回收。一旦持有它的类通过类加载器加载,它就会停留在那里。对于您的应用程序中的所有类,类加载器始终是相同的,并且它具有对您所有类的静态引用(例如MyInnerClass.class)。由于类加载器不会消失,你的类也不会这样做,因为它们被引用了,因此不能被垃圾回收。

就像你的例子

public class SomeClass extends SurfaceView 
  private static Context myContext;

  public MyInnerClass(Context context)
     myContext = context;        // This is bad.
  

确实很糟糕。即使不存在对SomeClass 的引用(例如,显示您的自定义SurfaceViewActivity 已结束),对Context 的静态引用(以及SomeClass 中的任何其他static 变量/常量)仍将保留。您可以认为所有这些都已泄漏,因为不可能对 Context 等进行垃圾收集。如果您有一个常规变量引用某些东西,那么一旦包含该变量的实例不再引用它,整个实例包括它对它的引用其他东西可以并且将会被垃圾回收。Java 甚至可以很好地处理循环引用。

对于您希望发生的常量,这通常还不错,因为常量的数量和它们占用的内存量并不大。常量也不(不应该)引用其他占用大量内存的实例,例如 ContextBitmap

除了通过静态变量创建内存泄漏的可能性之外,如果您不想同时为所有实例提供一个单一的东西,您也可能会产生问题。例如,如果您将SurfaceViewBitmap 保存在static 变量中,则不能有两个不同的图像。即使两个SurfaceViews 没有同时显示,您也可能会遇到问题,因为每个新实例可能会覆盖旧图像,如果您返回另一个SurfaceView,您会意外显示错误的图像。我几乎可以肯定你不想在这里使用static

您的内部类是 static class 的事实并不意味着您必须使用静态变量 - 它只是意味着它的行为更像 static 方法,因为它不能使用实例变量(那些不是static)。

为避免内存泄漏,您根本不应该使用静态变量。除非您做特殊的事情(例如计算类的实例),否则无需使用它们。常量很好。

【讨论】:

谢谢。我猜想要静态的总体原因是因为从概念上讲,大多数 View 对象只需要一个实例,因为它们通常代表特定的屏幕或屏幕上的对象。使它们成为静态的代码似乎更有效,但我理解那里的缺点。让变量完全受 GC 支配的唯一困扰我的是,我无法控制何时需要释放内存,而且从我在应用程序中看到的所有内容来看,它总是在位图操作期间,恐怕会变成抖动的帧速率。 你不需要有一些静态的东西来保持它的活力和重用它。只要您有对它的引用,它就会存在。如果您忘记适当地null 所有静态引用,使用静态通常会导致更多的内存消耗。 + 例如,如果您旋转屏幕,通常会重新创建视图实例 我保持屏幕方向锁定,但这是一个非常好的点。再次感谢! 很好的解释,我使用了对上下文的弱引用来避免内存泄漏【参考方案2】:

本文讨论可变静态字段:http://javabook.compuware.com/content/memory/problem-patterns/memory-leaks.aspx。基本上,避免使用它们并改用常量。

【讨论】:

以上是关于Android:静态字段和内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

Android技术分享|Android 中部分内存泄漏示例及解决方案

Kotlin伴侣对象中的内存泄漏?

Android性能优化之利用LeakCanary检测内存泄漏及解决办法(转)

Android性能优化之利用LeakCanary检测内存泄漏及解决办法(转)

安卓内存分析——常见内存泄漏场景二

此Loader类应该是静态的,否则可能会发生泄漏内部类内存泄漏| AsyncTaskLoader内存泄漏 -