A good programmer inevitable question: Memory Leak

Foreword

Memory leaks, a so big that under no small flaws. As developers, we are well aware of Memory leaks are caused by our code issue. But then again, the consequences will be very serious thing leak? This is not to say, if we do not leak memory of such a large Bitmap object, fixing memory leaks like a tasteless, 'tasteless gesture. " To say our project group, nearly 2000w's DAU, not markedly affect the user experience, all the more demand-driven ...

But this as a blessing code 996 farmers, not just digging, do not fill the hole, after all, technical debt are to repay. So today we chat to a memory leak in Android. This article summarizes the translation of foreign friends of the article: reads as follows

techbeacon.com/app-dev-tes…

First, the theoretical

First on a map:

A good programmer inevitable question: Memory Leak

Explain this picture, each Android (or a Java) applications have a starting point (GC Root), from this point instantiate the object, call the method. . Some objects direct reference to GC Root, other objects and references to these objects. Thus, a chain of references, like the figure the same. Therefore, the garbage collector begins GC Root and traverse directly or indirectly linked to GC Root object. At the end of the process, from the GC Root object / object chain will be recovered.

Then we think of another question:

What is a memory leak?

With the concept of the figure, a memory leak is very simple to understand, it means: A long lifetime of an object held by the object B short life cycle, as long as the A chain without departing from the GC Root, then the object B might never be recycling, so B would leak.

What is the harm?

Harm, then, such as opening said. If the leaked memory is very small, a few bytes, a few kb .... For now the machine performance, just like Star-Lord beat Thanos ... "harm" basic disregard. But if the leak is more than enough, ordinary GC can not recover these leaks memory, the heap will continue to increase, when the heap is large enough, it will trigger the "stop-the-world" GC, time-consuming directly in the main thread GC.

The main thread time-consuming operation, every android developers understand what that means ....

So the memory leak is severe enough, the harm is very serious.

Second, practice

For our daily development, there are more scenes a little attention there is a risk of memory leaks. Let's look at together:

2.1, inner classes Inner classes

内部类存在内存泄漏的风险,是一个老生常谈的话题。说白了就是因为我们在new一个内部类时,编译器会在编译时让这个内部类的实例持有外部对象。

这也就是,为啥我们的内部类可以引用到外部类变量、方法的原因。

上段代码:

public class BadActivity extends Activity {

    private TextView mMessageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_bad_activity);
        mMessageView = (TextView) findViewById(R.id.messageView);

        new LongRunningTask().execute();
    }

    private class LongRunningTask extends AsyncTask<Void, Void, String> {

        @Override
        protected String doInBackground(Void... params) {
            return "Am finally done!";
        }

        @Override
        protected void onPostExecute(String result) {
            mMessageView.setText(result);
        }
    }
}

大家应该都能看出这里的问题吧。作为非静态内部类的LongRunningTask,会持有BadActivity。并且LongRunningTask是一个长时间任务,也就是说,在这个任务没有完成时,BadActivity是不会被回收的,因此我们的BadActivity就被泄漏了。那么怎么改呢?

解决原理

首先我不能让LongRunningTask持有BadActivity。那么我们需要使用静态内部类(static class)。这样的确不会持有BadActivity,但是问题来了,我们LongRunningTask不持有BadActivity,也就意味着没办法引用到BadActivity中的变量,那么我们的更新UI的操作就做不了,也就是说还是要显示的传一个BadActivity中我们需要的变量进来…但是这样有造成了同样的泄漏问题。

因此,我们需要对传入的变量使用WeakReference进行包一层。但发生GC的时候,告诉GC收集器“我”可以被回收。

上改造后的代码:

public class GoodActivity extends Activity {

    private AsyncTask mLongRunningTask;
    private TextView mMessageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_good_activity);
        mMessageView = (TextView) findViewById(R.id.messageView);

        mLongRunningTask = new LongRunningTask(mMessageView).execute();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mLongRunningTask.cancel(true);
    }

    private static class LongRunningTask extends AsyncTask<Void, Void, String> {

        private final WeakReference<TextView> messageViewReference;

        public LongRunningTask(TextView messageView) {
            this.messageViewReference = new WeakReference<>(messageView);
        }

        @Override
        protected String doInBackground(Void... params) {
            String message = null;
            if (!isCancelled()) {
                message = "I am finally done!";
            }
            return message;
        }

        @Override
        protected void onPostExecute(String result) {
            TextView view = messageViewReference.get();
            if (view != null) {
                view.setText(result);
            }
        }
    }
}

2.2、匿名类 Anonymous classes

这一类和2.1很类似。本质都是持有外部对象的引用。

上一段很常见的代码:

public class MoviesActivity extends Activity {

    private TextView mNoOfMoviesThisWeek;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_movies_activity);
        mNoOfMoviesThisWeek = (TextView) findViewById(R.id.no_of_movies_text_view);

        MoviesRepository repository = ((MoviesApp) getApplication()).getRepository();
        repository.getMoviesThisWeek()
                .enqueue(new Callback<List<Movie>>() {

                    @Override
                    public void onResponse(Call<List<Movie>> call,
                                           Response<List<Movie>> response) {
                        int numberOfMovies = response.body().size();
                        mNoOfMoviesThisWeek.setText("No of movies this week: " + String.valueOf(numberOfMovies));
                    }

                    @Override
                    public void onFailure(Call<List<Movie>> call, Throwable t) {
                        // Oops.
                    }
                });
    }
}

2.3、注册Listener

SingleInstance.setMemoryLeakListener(new OnMemoryLeakListener(){
    //…..
})

这里写了段很常见的伪码,一个单例的对象,register了一个Listener,并且这个Listener被单例的一个成员变量引用。

OK,那么问题很明显了。单例作为静态变量,肯定是一直存在的。而其内部持有了Listener,而Listener作为一个匿名类,有持有了外部对象的引用。因此这条GC链上的所有对象都不会被释放。

Solution is very simple, the right time, in a single embodiment Listener references set to null. Thus, the relationship between the reference and the single embodiment Listener broken, all content on the Listener chain can be released out of the normal. That is, we often do in onDestory()the operation of unRegisterListener.

Similar pay attention to the content, but also Lambda. But one thing is worth noting that, in Kotlin of Lambda, if we do not use the variable or method of an external object, then Kotlin at compile time, this is not going to hold Lambda external object references. It can be considered some optimization Kotlin it

2.4、Contexts

The context of the abuse, but also the leakage of large customers. But it should be more familiar for this kind of problem.

For example: long live objects, not recommended to hold context Activity, but using ApplicationContext. If ApplicationContext no way to complete the business, then you need to think about: the long survival of the object, why it is necessary to hold the context Activity. Its design is reasonable, whether it should be a long-lived objects (such as single cases).

end

About memory leaks, we still need to pay more attention on their own to write every line of code more thinking. After all, this stuff "is not a disease, but the pain is so terrible."

Well, the article to end here if you feel the article fairly useful, may wish to recommend them to your friends.

Guess you like

Origin blog.51cto.com/14332859/2446919