Never initialize an instance with double curly braces, otherwise it will oom!

Embarrassment is everywhere in life. Sometimes you just want to pretend, but some "old comrades" always inadvertently give you a ruthless kick and kick you out of breath.

But who makes us young? If you lose early, the road ahead will be better.

After drinking this warm chicken soup, let's talk about what's going on.

The thing is, in a small 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);
});

It was originally used 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);
});

The execution results of the two pieces of code are exactly the same:

So Xiao Wang is proudly introducing this code to Xiao Tiantian, the new sister of the Department, but Lao Zhang who is passing by also sees it.

Lao Zhang just wanted to add a cup of 85 ° hot water to yesterday's medlar, but it was a coincidence that he just bumped into a wave of opportunities to show his technology in front of Xiaotiantian, so he habitually sorted out his sparse hair and started the diss mode.

"Xiao Wang, you have a big problem with this code!"

"How can I initialize an instance with double curly braces?"

At this time, Xiao Wang was asked with a confused face. Countless grass and mud horses rushed past in his heart. He thought that your old cow even competed with me for this tender grass, but he had an ominous hunch in his heart. He felt that he was going to lose. He was shy and didn't know what to say. He could only blush and gently "huh?" A cry.

Lao Zhang: "initializing an instance with double curly braces will lead to memory overflow! Don't you know?"

Xiao Wang was silent for a moment, but according to his past experience, the "old guy" still had something, so he gave a perfunctory "Oh ~" as if he understood what was going on. In fact, he was still confused. In order not to be discovered by other colleagues, he had to behave like this.

So a moment of perfunctory, after Lao Zhang left, he quietly opened Google and searched silently.

Xiao Wang: Oh, I see

Double curly brace initialization analysis

First, let's look at the essence of initialization with double curly braces?

Take this code as an example:

Map<String,"value3");
}};

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

For this point, we can use the command javac to compile the code into bytecode. We find that the previous class is compiled into two bytecode (. Class) files, as shown in the following figure:

We use idea to open doublebracket $1 Class file discovery:

import java.util.HashMap;

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

At this point, we can confirm that it is an anonymous inner class. So the question is, why does anonymous inner class cause memory overflow?

"Pot" of anonymous inner class

In the Java language, non static internal classes will hold references to external classes, resulting in GC unable to reclaim references to this part of the code, resulting in memory overflow.

Think 1: why hold external classes?

This starts with the design of anonymous inner classes. In the Java language, non static anonymous inner classes play two main roles.

1. When the anonymous inner class is only used in the external class (main class), the anonymous inner class can make the external do not know its existence, thus reducing the maintenance of the code.

2. When the anonymous internal class holds the external class, it can directly use the variables in the external class, which can easily complete the call, as shown in the following code:

public class DoubleBracket {
    private static String userName = "磊哥";
    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);
        }};
    }
}

As can be seen from the above code, the variable username of the external class can be directly used inside the HashMap method.

Thinking 2: how does it hold external classes?

About how anonymous inner classes persist external objects, we can see by looking at the bytecode of anonymous inner classes. We use javap - C doublebracket \ $1 Class command, where $1 is the bytecode of anonymous class, and the contents of bytecode are as follows;

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
}

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

If you think the above bytecode is not intuitive enough, it doesn't matter. Let's use the following actual code 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();
        // 获取一个类的所有字段
        Field field = map.getClass().getDeclaredField("this$0");
        // 设置允许方法私有的 private 修饰的变量
        field.setAccessible(true);
        System.out.println(field.get(map).getClass());
    }
    public Map createMap() {
        // 双花括号初始化
        Map map = new HashMap() {{
            put("map1","value3");
        }};
        return map;
    }
}

When we turn on the debugging mode, we can see that the external object doublebracket is held in the map, as shown in the following figure:

The execution result of the above code is:

From the above program output results, we can see that the anonymous internal class holds the reference of the external class, so we can use $0 to normally obtain the external class and output the relevant class information.

What can cause a memory leak?

When we put the following normal code:

public void createMap() {
    Map map = new HashMap() {{
        put("map1","value1");
        put("map2","value2");
        put("map3","value3");
    }};
    // 业务处理....
}

Changing to the following may cause memory leakage:

public Map createMap() {
    Map map = new HashMap() {{
        put("map1","value3");
    }};
    return map;
}

Why does using "may" instead of "must" cause memory leakage?

This is because when this map is assigned to other class attributes, this object may not be cleaned up during GC collection, which will lead to memory leakage. You can pay attention to my "JAVA Chinese community" and will write an article on this issue later.

How to ensure that memory does not leak?

To ensure that the double buckle number does not leak, the method is also very simple. You only need to declare the map object as static. The code is as follows:

public static Map createMap() {
    Map map = new HashMap() {{
        put("map1","value3");
    }};
    return map;
}

what? You don't believe it!

It doesn't matter. Let's speak with facts. Using the above code, we recompile a bytecode and check 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
}

From this code, we can see that there is no putfield keyword, that is, static anonymous classes will not hold references to external objects.

Why don't static inner classes hold references to outer classes?

The reason is actually very simple. After the anonymous internal class is static, the object or attribute it references must also be static. Therefore, you can directly obtain the reference from the JVM's method area without persisting the external object.

Alternative to double curly braces

Even though static variables can avoid memory leakage, they are still not recommended. Why?

The reason is very simple. Projects generally need teamwork. What if that guy deletes your static without knowing it? This is equivalent to setting an invisible "pit". Other people who don't know jump in accidentally, so we can try some other schemes, such as the stream API in Java 8 and the collection factory in Java 9.

Alternative 1: stream

Use the stream API in Java 8 instead, as shown below. Original code:

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

Substitution code:

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

Alternative 2: assembly plant

Use the of method of the collection factory instead, as shown below. Original code:

Map map = new HashMap() {{
    put("map1","value2");
}};

Substitution code:

Map map = Map.of("map1","Java","map2","Redis");

Obviously, the scheme in Java 9 is very suitable for us. It's simple and cool, but it's a pity that we still use Java 6 6... 6... A broken heart.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>