The embarrassment in life is everywhere, sometimes you just want to simply pack a, but some “old comrades” always inadvertently, give you a merciless kick, kick you simply can’t breathe.

But who made us young? Suffer as early as possible, the road ahead will be better.

After drinking this warm chicken soup, let’s talk about what happened.

Here’s the thing: On a modest project, Xiao Wang wrote this code:

Map<String, String> map = new HashMap() {{
    put("map1"."value1");
    put("map2"."value2");
    put("map3"."value3");
}};
map.forEach((k, v) -> {
    System.out.println("key:" + k + " value:" + v);
});
Copy the code

This is supposed to replace the following code:

Map<String, String> map = new HashMap();
map.put("map1"."value1");
map.put("map2"."value2");
map.put("map3"."value3");
map.forEach((k, v) -> {
    System.out.println("key:" + k + " value:" + v);
});
Copy the code

The two pieces of code will execute exactly the same:

key:map3 value:value3

key:map2 value:value2

key:map1 value:value1

So wang is proud to introduce this section of code to the department of new sister Little sweet see, but unfortunately is passing by the old zhang also saw.

Lao Zhang originally just wanted to give yesterday’s wolfberry and a cup of 85° hot water, but it happens that just hit a wave of opportunity to show technology in front of the Small sweet, so the habit of finishing his sparse hair, then opened the DISS mode.

“Small wang, you this code problem is very big!”

“How can I initialize an instance with double curly braces?”

At this time the xiao Wang was asked of a face meng, the heart has countless grass mud horse pentium and pass, the heart think you this old cow even and I contend for this tender grass, but the heart has a kind of foreboding, feel oneself to lose, the moment shy of I do not know what to say, can only red small face, gently “HMM?” A sound.

Lao Zhang: “Using double curly braces to initialize an instance will overflow memory! Don’t you know?”

Xiao Wang was silent for a while, just by virtue of the past experience, this “old guy” or a bit of things, so perfunctory “oh ~” a sound, as if he understood how to go on, in fact, the heart is still confused of a, in order not to let other colleagues found, had to do so.

So a moment of perfunctory, after Mr. Zhang left, quietly opened Google, silently search.

Xiao Wang: Oh, I see……

Double curly braces initialize analysis

First of all, what is the nature of initialization with double curly braces?

Take our code for example:

Map<String, String> map = new HashMap() {{
    put("map1"."value1");
    put("map2"."value2");
    put("map3"."value3");
}};
Copy the code

This code creates the anonymous inner class and then initializes the block.

We can do this by using the command javac to compile the code to bytecode, and we find that a previous class has been compiled into two bytecode (.class) files, as shown in the figure below:

Class = ‘DoubleBracket$1.class’;

import java.util.HashMap;

class DoubleBracketThe $1extends HashMap {
    DoubleBracket$1(DoubleBracket var1) {
        this.this$0 = var1;
        this.put("map1"."value1");
        this.put("map2"."value2"); }}Copy the code

At this point we can confirm that it is an anonymous inner class. So why do anonymous inner classes cause memory overruns?

The “pot” of anonymous inner classes

In the Java language, non-static inner classes hold references to external classes, causing the GC to fail to reclaim references to this part of the code, resulting in a memory overflow.

Thought 1: Why hold external classes?

This starts with the design of anonymous inner classes. In the Java language, non-static anonymous inner classes serve two main purposes.

When an anonymous inner class is only used in an outer class (the main class), an anonymous inner class can make the outer class unaware of its existence, thus reducing code maintenance.

2. When an anonymous inner class holds an outer class, it can directly use variables in the outer class. This makes it easy to complete calls, as shown in the following code:

public class DoubleBracket {
    private static String userName = "Leilei";
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Map<String, String> map = new HashMap() {{
            put("map1"."value1");
            put("map2"."value2");
            put("map3"."value3"); put(userName, userName); }}; }}Copy the code

From the code above, you can see that inside the HashMap method, you can directly use the userName variable of the outer class.

Thought 2: How does it hold external classes?

Javap -c DoubleBracket\$1. Class indicates an anonymous class’s bytecode. $1 indicates an anonymous class’s bytecode.

javap -c DoubleBracket\$1.class Compiled from "DoubleBracket.java" class com.example.DoubleBracket$1 extends java.util.HashMap { final com.example.DoubleBracket this$0; com.example.DoubleBracket$1(com.example.DoubleBracket); Code: 0: aload_0 1: aload_1 2: putfield #1 // Field this$0:Lcom/example/DoubleBracket; 5: aload_0 6: invokespecial #7 // Method java/util/HashMap."<init>":()V 9: aload_0 10: ldc #13 // String map1 12: ldc #15 // String value1 14: invokevirtual #17 // Method put:(Ljava/lang/Object; Ljava/lang/Object;) Ljava/lang/Object; 17: pop 18: aload_0 19: ldc #21 // String map2 21: ldc #23 // String value2 23: invokevirtual #17 // Method put:(Ljava/lang/Object; Ljava/lang/Object;) Ljava/lang/Object; 26: pop 27: return }Copy the code

The key is the putfield line, which indicates that a reference to a DoubleBracket is stored in this$0, meaning that the anonymous inner class holds a reference to the outer class.

If the bytecode above is not intuitive to you, let’s use the actual code below to prove it:

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class DoubleBracket {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Map map = new DoubleBracket().createMap();
        // Get all fields of a class
        Field field = map.getClass().getDeclaredField("this$0");
        // Set the variable that allows the method to be private
        field.setAccessible(true);
        System.out.println(field.get(map).getClass());
    }
    public Map createMap(a) {
        // Double curly bracket initialization
        Map map = new HashMap() {{
            put("map1"."value1");
            put("map2"."value2");
            put("map3"."value3");
        }};
        returnmap; }}Copy the code

When debugging mode is enabled, we can see that the map has an external DoubleBracket object, as shown in the figure below:

The result of the above code is:

class com.example.DoubleBracket

As we can see from the output above, the anonymous inner class holds a reference to the outer class, so we can use $0 to get the outer class normally and output the relevant class information.

What causes a memory leak?

When we put the following normal code (no return value) :

public void createMap(a) {
    Map map = new HashMap() {{
        put("map1"."value1");
        put("map2"."value2");
        put("map3"."value3");
    }};
    // Service processing....
}
Copy the code

Changing to something like this (which returns a Map set) may cause a memory leak:

public Map createMap(a) {
    Map map = new HashMap() {{
        put("map1"."value1");
        put("map2"."value2");
        put("map3"."value3");
    }};
    return map;
}
Copy the code

Why does using “may” instead of “must” cause a memory leak?

This is because when the map is assigned to other class attributes, it may cause the object not to be cleaned up during GC collection, which can cause a memory leak. Follow me on the Java Chinese Community for a post on this topic.

How do I prevent memory leaks?

}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}

public static Map createMap(a) {
    Map map = new HashMap() {{
        put("map1"."value1");
        put("map2"."value2");
        put("map3"."value3");
    }};
    return map;
}
Copy the code

What? You don’t believe it!

Using the above code, we recompile a bytecode and look at the contents of the anonymous class as follows:

javap -c DoubleBracket\$1.class Compiled from "DoubleBracket.java" class com.example.DoubleBracket$1 extends java.util.HashMap { com.example.DoubleBracket$1(); Code: 0: aload_0 1: invokespecial #1 // Method java/util/HashMap."<init>":()V 4: aload_0 5: ldc #7 // String map1 7: ldc #9 // String value1 9: invokevirtual #11 // Method put:(Ljava/lang/Object; Ljava/lang/Object;) Ljava/lang/Object; 12: pop 13: aload_0 14: ldc #17 // String map2 16: ldc #19 // String value2 18: invokevirtual #11 // Method put:(Ljava/lang/Object; Ljava/lang/Object;) Ljava/lang/Object; 21: pop 22: aload_0 23: ldc #21 // String map3 25: ldc #23 // String value3 27: invokevirtual #11 // Method put:(Ljava/lang/Object; Ljava/lang/Object;) Ljava/lang/Object; 30: pop 31: return }Copy the code

As you can see, the putfield keyword line is gone, so static anonymous classes do not hold references to external objects.

Why does a static inner class not hold a reference to an outer class?

The reason is simple: once an anonymous inner class is static, the object or property it references must also be static, so you can get the reference directly from the JVM’s Method Area without having to persist the external object.

Alternative to double curly braces

Even if declaring variables as static can avoid memory leaks, it is not recommended to use them. Why?

The reason for this is simple. Projects are usually a team effort. What if the guy deleted your static without knowing it? This creates an invisible “pit” into which people who don’t know can jump, so we can try other solutions such as the Stream API in Java8 and the collection factory in Java9.

Alternative 1: Stream

Use the Stream API in Java8 instead, as shown in the following example. The original code:

List<String> list = new ArrayList() {{
    add("Java");
    add("Redis");
}};
Copy the code

Alternative code:

List<String> list = Stream.of("Java"."Redis").collect(Collectors.toList());
Copy the code

Alternative 2: Assembly factories

Use the collection factory’s OF method instead, as shown in the following example. The original code:

Map map = new HashMap() {{
    put("map1"."value1");
    put("map2"."value2");
}};
Copy the code

Alternative code:

Map map = Map.of("map1"."Java"."map2"."Redis");
Copy the code

Obviously using Java9 works for us, it’s simple and cool, but we’re still using Java 6… 6… 6… Heartbroken a place.

conclusion

In this article, we discussed the problem of double-curly bracket initialization that can cause memory leaks because it holds references to external classes, and demonstrated the problem at both the bytecode and reflection levels.

There’s a simple way to make sure that you don’t leak memory when you initialize double curly braces. You just need to make it static, but there’s a risk that someone might accidentally delete it, so let’s find another way to say, Found an alternative to “{{” using Stream in Java8 or the collection factory of method in Java9.

The last word

Original is not easy, click “like” and then go!

Reference & thanks

www.ripjava.com/article/129…

Cloud.tencent.com/developer/a…

Hacpai.com/article/149…

Follow the public account “Java Chinese community” reply “dry goods”, get 50 original dry goods Top list.