实例中的内存泄漏

时间:2015-09-04 14:40:02

标签: java android performance memory memory-leaks

我一直在努力识别内存泄漏。我想我的项目circular progress view中有几个内存泄漏。

我的一个猜测是内部类FadeRunnable中有内存泄漏。 但说实话,我不确切知道如何确定这是否是问题的根源。好吧,当我按常规方案切换方向时,我看到内存使用量增加,如下所示。如果我注释掉FadeRunnable类的用法,步骤会更小(但仍然存在,所以我猜这不是唯一的泄漏

memory steps

一旦我分析堆转储,我就会看到一些东西。但实际上我不知道这些值是什么意思。我做的事情是

  1. 多次更改方向
  2. 打开堆转储并按“保留大小”排序
  3. 现在,当我点击“CircularProgressView”时,我在右侧区域看到了8行。我猜这意味着有8个'CircularProgressView'实例泄露并在内存中作为孤儿存在。
  4. 这是对的吗?如果是这样,我怎样才能找到保存/保存此对象的转储信息(我想在下方窗格的某处)。

    heap dump

    我希望逐步解释如何找出是否以及哪个对象泄漏了一些记忆。

    怀疑视图的所有代码都可以在本课程中找到。

    https://github.com/momentummodules/CircularProgressView/blob/master/circularprogressview/src/main/java/momentum/circularprogressview/CircularProgressView.java

    但是也可以随时查看整个项目以获得更深入的洞察力,以及是否想要使用它。

    提前致谢!

    更新

    上面的代码链接显示了内存泄漏内部类的固定代码。以下代码段显示了永远不应该使用的原始内存代码

    /**
     * Mem-leaking code, for fixed code see repository link
     * https://github.com/momentummodules/CircularProgressView/blob/master/circularprogressview/src/main/java/momentum/circularprogressview/CircularProgressView.java
     */
    public class CircularProgressView extends View
    {
        ...
        private Thread fadeThread = null;
        ...
    
        ...
        class FadeRunnable implements Runnable
        {
            @Override
            public void run()
            {
                ...
            }
        }
        ...
    
        ...   
        private void startFade(boolean fadeIn)
        {
            // check existing
            if(this.fadeThread != null)
            {
                // check if fade is already running
                switch(this.fadeThread.getState())
                {
                    case TERMINATED:
                    case NEW:
                        this.fadeThread = null;
                        break;
                    case RUNNABLE:
                    case BLOCKED:
                    case TIMED_WAITING:
                    case WAITING:
                        return;
                }
            }
            // create new
            this.fadeThread = new Thread(new FadeRunnable(fadeIn, this.fadeTime));
            this.fadeThread.start();
        }
    }
    

2 个答案:

答案 0 :(得分:5)

是的,FadeRunnable课程确实存在内存泄漏。

内部类的每个实例都包含对其外部类的隐式引用,可通过OuterClass.this运算符访问。在您的项目中,当您执行FadeRunnable然后通过方向更改触发重新配置时,整个活动和包含在其中的CircularProgressView将被重新创建,但之前的FadeRunnable仍然存在(已分配)并且,由于它持有对其外部CircularProgressView类的隐式引用,视图也继续存在,这就是为什么在几次重新配置之后,你在内存中分配了8个CircularProgressView个实例,并且变得更糟 - 每个View都会保留对它的上下文的引用,而且这也无法释放,导致内存泄漏。

Runnables,Handlers和类似的对象可以将其封闭的活动,片段,视图等用作标准类或STATIC内部类(静态内部类不包含对其外部类的隐式引用) ),并且不应该保留ContextView等引用,而是在WeakReference<>通过配置更改重新创建Activity时保留View,可以通过垃圾收集器销毁和释放app.js

This是关于这个主题的非常翔实的文章,我强烈建议阅读它。

答案 1 :(得分:2)

我猜你的方向正确。这FadeRunnable肯定不酷。即使你有其他内存泄漏,你也应该检查一下。

一般来说,你在视图中应该做的事情是完全不同的,特别是视图已经有了处理时间和动画的工具,而不需要线程。

我会建议你,我认为这是一种更简单,更清晰的方法,可以在视图上制作动画。

  • 首先完全删除您的runnable和线程。

然后开始动画:

ValueAnimator animation = ValueAnimator.ofFloat(0, 1);
animation.setDuration(500);
animation.addUpdateListener(animationUpdate);
animation.addListener(animationUpdate);
animation.start();

然后你需要那些听众

   // this gets called for every animation update,
   // inside this call you update `CircularProgressView.this.fadeAlpha`
   private final ValueAnimator.AnimatorUpdateListener animationUpdate = new ValueAnimator.AnimatorUpdateListener() {
      @Override public void onAnimationUpdate(ValueAnimator animation) {
         // this fraction varies between 0f and 1f
         float fraction = animation.getAnimatedFraction();
         // ... do your calculation

         ViewCompat.postInvalidateOnAnimation(CircularProgressView.this);
      }
   };

   // this is an optional one only if you really need
   // in that you get notified when the animation starts and ends
   private final Animator.AnimatorListener animationListener = new AnimatorListenerAdapter() {

  @Override public void onAnimationStart(Animator animation) {
       // anything u need goes here
      ViewCompat.postInvalidateOnAnimation(CircularProgressView.this);
  }

      @Override public void onAnimationEnd(Animator animation) { 
      // anything u need goes here   
      ViewCompat.postInvalidateOnAnimation(CircularProgressView.this);
      }
   };

就是这样。

关于实际内存泄漏分析的主题我建议你从现在开始并永远使用泄漏金丝雀库:https://github.com/square/leakcanary这是一个很好的工具来帮助我们(开发人员)跟踪内存泄漏。

修改

为什么这个动画有内存泄漏? 这很简单:

  • startFade(boolean);上创建一个新线程和一个新的runnable
  • runnable具有对视图的引用(因为它是非静态内部类)
  • 线程引用了Runnable,因此可以运行它。
  • 框架会破坏视图,因为它不再是UI的一部分(旋转,后退按钮)
  • 线程仍然在运行,runnable仍在循环,View对象仍然没有被销毁,因为Runnable引用它。
  • 视图对象的实例为Context,此上下文为Activity

因此,在此序列结束时,您的活动不会被GC垃圾收集,AKA:内存泄漏!