[JAVA] An implicit reference to an enclosing object held by a non-static nested class

This article describes "Be careful, because non-static nested classes grab an enclosing object as an implicit reference" in Java.

The reason why you have to be careful is that it increases the possibility of causing a memory leak.

And the bottom line is, "Nested classes are better off with the static modifier (because they don't hold the enclosing object as an implicit reference). " Enclosing objects are short-lived, especially if instances of nested classes are longer-lived!

The reason for this was when developing an Android application

(This article is not specific to Android application development, but as you read it, you will be involved in Java, so please be patient at first.)

It was one day. When I was making an Android app in Java, Android Studio [^ 1] issued a warning like this.

[^ 1]: IDE for Android application development made by modifying IntelliJ IDEA

thisasynctaskclassshouldbestaticorleaksmightoccur.png

This 'AsyncTask' class should be static or leaks might occur

This class named CouponAsyncTask is defined as a nested class (inner class / inner class), but the balloon says "Please add a static modifier, otherwise it may cause a memory leak". I am. Well, I'm scared.

If you look at the detailed explanation that the code check function "Lint" that Android Studio has,

Static Field Leaks A static field will leak contexts. Non-static inner classes have an implicit reference to their outer class. If that outer class is for example a Fragment or Activity, then this reference means that the long-running handler/loader/task will hold a reference to the activity which prevents it from getting garbage collected. Similarly, direct field references to activities and fragments from these longer running instances can cause leaks. ViewModel classes should never point to Views or non-application Contexts. Issue id: StaticFieldLeak

I will ask Google translation.

Static field leak Static fields leak context. Non-static inner classes have implicit references to outer classes. If the outer class is, for example, a fragment or activity, this reference means that the long-running handler / loader / task holds a reference to the activity that prevents it from getting garbage collection. Similarly, direct field references to activities and fragments from these longer running instances can cause leaks. The ViewModel class does not point to a view or non-application context. Problem ID: StaticFieldLeak

Even if a difficult English sentence is translated into a difficult Japanese ... It's Chimpung Kampung.

It was written on the developer site

It was properly posted on the official website for Android application developers as "an example of a common flaw in code design using threaded objects". I wonder if there is. Improvement of performance by threading [implicit reference]

On the Japanese page of this official website, it is translated as "implicit references", but the original text is written as "Implicit references". In this article, we will use five-character idioms as "implicit reference."

Nested class

Even if I suddenly talk about "implicit reference of enclosing object held by non-static nested class", there is a lot of information on this subject, so I will explain the prerequisite knowledge one by one. Hurry up. However, it may be a little frustrating.

A "nested class" is also known as an "inner class". This article is unified with "nested class".

Foo class has two nested classes


public class Foo {
    class Bar {
        void methodBar() {
            System.out.println("methodBar");
        }
    }

    static class Baz {
        void methodBaz() {
            System.out.println("methodBaz");
        }
    }

    void methodFoo() {
        System.out.println("methodFoo");
    }
}

in this case,

--The Bar and Baz classes are called "foo class nested classes". ――Foo is called "Bar class enclosing class".

The "enclosing class" is also known as the "outer class". This article will be unified with "Enclosing Class".

The difference between Bar and Baz is whether or not they have the static modifier.

--The Bar class is called "non-static nested class of Foo class". For those who find it annoying, the "Foo class nesting class" is enough. --The Baz class is called "static nested class of Foo class".

To instantiate a nested class

Instantiate two nested classes of Foo


class Main {
    public static void main(String[] args) {
        Foo foo = new Foo();
        Foo.Bar bar = foo.new Bar();

        Foo.Baz baz = new Foo.Baz();
    }
}

When instantiating a non-static nested class

It took two lines to instantiate the Bar class. If you really want to do it in one line, use the following code.

Instantiate a Bar class in one line


Foo.Bar bar = new Foo().new Bar();

In any case, if you want to instantiate a non-static nested class, you need an instance of enclosing. And it is also characteristic that . is added before the new operator.

When creating an instance of a static nested class

You don't need an instance of enclosing in advance. You can suddenly new.

What is unique is that the class name is " Foo.Baz "instead of" Baz". It is characteristic that . is included between the class names.

Corresponds to the book "Effective Java"

Effective java started from the first edition (published in 2001), "Static member classes rather than non-static member classes. I have a lot of things to choose. It is the Goseibai Shikimoku that remains even in the third edition published in 2018.

Memory leaks can be catastrophic.

And

Memory leaks are usually difficult to detect.

And

Keep extra references and waste memory and time.

Such as, scary things are written. Because

** If you omit the static modifier, each instance will have an irrelevant reference to the enclosing object. Saving that reference takes time and memory. The serious thing is that without that reference, the enclosing instance can be left behind if it is subject to garbage collection. ** **

Is written.

Enclosure instances are no longer needed, but are not called to heaven

I modified the above program a little. The static nested class Baz has been dismissed a bit.

python


public class Main {
    public static void main(String[] args) {
        Foo foo = new Foo();
        Foo.Bar bar = foo.new Bar();

        foo = null;
        System.gc();
//        foo.methodFoo(); //NullPointerException occurred
        bar.methodBar();
    }
}

I assigned null to the Foo instance of the enclosing class to perform garbage collection (GC), and then called the method to the Bar instance of the non-static nested class. When I try to execute it, it is as follows.

Execution result


methodBar

Yeah, it works! I glanced at it.

As a Java spec, no matter how much I assign a null to an instance of the enclosing class Foo, the object named foo will work as long as the Bar instance of that non-static nested class works. It is not GC (it was brilliantly passed through without being considered as a GC target). Because ** bar holds foo as an implicit reference **.

If you make an illustration as a funny analogy, it looks like this.

joubutudekinai.png

The woman who is climbing the stairs step by step is full of energy at bar.

On the other hand, the old man who wants to be a Buddhahood is foo, but because bar holds it, he is not easily called to heaven. It can be a stone tape.

--Gripping = referring --Called to heaven = released from memory by GC --Stone Tape = Memory Leak

Please read as.

Then, just add static to the nested class in a short circuit and everything will be solved! Are you going?

Stop using Foo, Bar, and Baz and see the program below.

Before: Nested class is non-static


class Fizz {
    private Integer x = 12;
    private static Integer y = 34;

    class Buzz {
        void m() {
            x = 56;
            y = 78;
            System.out.println(x + y);
        }
    }
}

class FizzBuzzMain {
    public static void main(String[] args) {
        Fizz.Buzz fb = new Fizz().new Buzz();
        fb.m();
    }
}

Before execution result


134

It's a non-static nested class, even though it's (intentionally) done. Now, let's simply add the static modifier to this nested class named Buzz.

After


class Fizz {
    private Integer x = 12;
    private static Integer y = 34;

    static class Buzz {
        void m() {
            x = 56; //Cannot compile because x is not a static field
            y = 78;
            System.out.println(x + y); //Cannot compile because x is not a static field
        }
    }
}

class FizzBuzzMain {
    public static void main(String[] args) {
        Fizz.Buzz fb = new Fizz.Buzz();
        fb.m();
    }
}

Immediately I couldn't compile. The IDE gets angry if it doesn't work because x is not a static field.

Why. That's because ** static nested classes can only access static members of enclosing **.

Let's return to the story of Android application development again.

Please see this animated GIF for the Android app you wanted to make.

countup.gif

As soon as you start it, the count-up will start automatically. This animated GIF is an animation that repeats START! When it reaches 3, but in reality, this app starts from START! And counts up to "9" and ends (keeps outputting "9"). ..

And the program is as follows. Even those who have no experience in Android application development should be patient.

Screen class


public class MainActivity extends AppCompatActivity {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.text_view);
        new MyTask().execute();
    }

    class MyTask extends AsyncTask<Void, String, Void> {
        @Override
        protected Void doInBackground(Void... voids) {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                    publishProgress(String.valueOf(i));
                } catch (InterruptedException e) {
                    Log.e("MyTask", e.getMessage(), e);
                }
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(String... values) {
            textView.setText(values[0]);
        }
    }
}

If you want to create a "screen class" on Android, start coding a class that inherits ʻAppCompatActivity`.

The class TextView is a view (part) that displays (only can) character strings that are placed in the center of the screen and display" START! "," 0 ", or" 1 ". I want to change this TextView from" 0 "to" 1 "and then to" 2 "in 1000 milliseconds by asynchronous processing. This is the requirement for this app.

If you want asynchronous processing, define a subclass of ʻAsyncTask` and code the processing in it.

MainActivity_MyTask.png

If you call the ʻexecute ()method onMyTask that performs the asynchronous processing, MyTaskwill start processing. The red box in this image isTextView. MyTask overwrites this TextViewwith a string such as" 0 ", " 1 ", or " 2 "`. This makes it look like it's counting up.

Now, let's add static to the MyTask class.

Non-static field.png

The IDE got angry red. Because the field named textView doesn't have the static modifier!

I don't need an instance of MainActivity anymore, but it's not called to heaven

Please take a look at the illustration of the woman climbing the stairs and the grandfather of the stone tape.

On Android, screens that are no longer needed are GC'd. The "unnecessary screen" is when the user hides (turns off) the screen by pressing the back button. In general, Android devices have less memory than PCs [^ 2]. Therefore, the screen = Activity class instance hidden by the user is quickly GCed by the runtime.

[^ 2]: There are Android "gaming" smartphones on the market with 10GB of RAM, but that's aside.

The user simply presses the back button, and that screen = Activity instance is unnecessary! Nja GC! It tends to be short-lived and fateful. So, in the background (no, the term "background" isn't right, it's better to say "asynchronous processing in worker threads") MyTask is my own You will live until you have completed your work (count up).

It is an IDE called Android Studio that warned me that it was an [enclosing instance <instance of a nested class] in this comparison of survival time.

Start this app I made, and even if the count-up starts with the work of MyTask, the user presses the back button to hide the screen (turn it off, stop the app) Even so, ** the MyTask object, which is an instance of the non-static nested class, holds an implicit reference to the MainActivity instance **, so it shouldn't be displayed anymore, so it should be GC. In addition, it falls into a state where GC is not performed. Where the user is not visible, MyTask is constantly doing count-up work. In the illustration, the woman MyTask climbs one step at a time. But in his right hand, he firmly holds the grandfather Main Activity, who can't be a Buddhahood. The user can't see this grandfather.

Even if you want to solve it

I am struck by the dilemma here.

--This MyTask is a class that is used only in this MainActivity, so I want to make it a nested class. --But the IDE warns you to make it a static nested class. --But I don't want (and shouldn't) make a field of type TextTview a static field. --So, is this MyTask and this MainActivity divided into different classes? ――But in that case, passing the reference of TextTview is troublesome (this time, if this passing is not good, TextTview may be a source of memory leak). --It is MainActivity that has (should have) TextTview, and it is MyTask that changes the string of that TextTview. ――So, after all, I want this MyTask to be a nested class of this MainActivity. -java.lang.ref.WeakReference <T> Do you use ...

I've been worried about this all the time. You may have to compromise something somewhere.

that's all. I would like to write this article in the words of Dr. Fukuzawa Yukichi, as I am worried about my conscience, whether it is okay to end this.

However, the pain of being struck by a bad child is more painful than the hellish sword, and I have no choice but to hit this sword with my current body.

_ Fukuzawa Yukichi "Education" 1878 (Meiji 11) _

Hey Yukichi, "It's more painful than hellish blame" is too severe to draw. quit. I'm going to talk to Mr. Watsuji.

Before Dogen's mercy, "evil is not necessarily something to be ridiculed."

_ Tetsuro Watsuji "Study of Japanese Spiritual History" 1922 (Taisho 11) _

that's all.

Recommended Posts

An implicit reference to an enclosing object held by a non-static nested class
Mapping to a class with a value object in How to MyBatis