The Beauty of Design Patterns 55-Flyweight Pattern (Part 2): Analysis of the Application of Flyweight Pattern in JavaInteger and String

55 | Flyweight mode (Part 2): Analysis of the application of Flyweight mode in Java Integer and String

In the last class, we learned the principle, implementation and application scenarios of Flyweight mode through two practical examples such as chess and card games and text editors. To sum up in one sentence, the "flyweight" in the flyweight mode refers to the shared unit. Flyweight mode saves memory by reusing objects.

Today, I will take you another lesson to analyze the application of flyweight mode in Java Integer and String. If you are not familiar with the Java programming language, don't worry about not understanding it, because today's content mainly introduces design ideas, and has little to do with the language itself.

Without further ado, let's officially start today's study!

Application of Flyweight Mode in Java Integer

Let's first look at the following piece of code. You can first think about what kind of results this code will output.

Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2);
System.out.println(i3 == i4);

If you are not familiar with the Java language, you may think that the values ​​of i1 and i2 are both 56, and the values ​​of i3 and i4 are both 129. The values ​​of i1 and i2 are equal, and the values ​​of i3 and i4 are equal, so the output result should be two true. This kind of analysis is wrong, mainly because you are not familiar with Java syntax. To correctly analyze the above code, we need to figure out the following two questions:

  • How to determine whether two Java objects are equal (that is, the meaning of the "==" operator in the code)?
  • What is Autoboxing and Unboxing?

In Add Meal 1 , we mentioned that Java provides corresponding wrapper types for basic data types. Specifically as follows:

insert image description here

The so-called autoboxing is to automatically convert the basic data type into a wrapper type. The so-called automatic unboxing is to automatically convert the wrapper type into a basic data type. A specific code example is as follows:

Integer i = 56; //自动装箱
int j = i; //自动拆箱

The value 56 is the basic data type int. When it is assigned to a wrapper type (Integer) variable, the automatic boxing operation is triggered, an object of type Integer is created, and the value is assigned to the variable i. Its bottom layer is equivalent to executing the following statement:

Integer i = 59;底层执行了:Integer i = Integer.valueOf(59);

Conversely, when the variable i of the wrapper type is assigned to the variable j of the basic data type, an automatic unboxing operation is triggered, and the data in i is taken out and assigned to j. Its bottom layer is equivalent to executing the following statement:

int j = i; 底层执行了:int j = i.intValue();

After figuring out autoboxing and autounboxing, let's look at how to determine whether two objects are equal? However, before that, we must first figure out how Java objects are stored in memory. Let's illustrate with the following example.

User a = new User(123, 23); // id=123, age=23

For this statement, I drew a memory storage structure diagram, as shown below. The value stored in a is the memory address of the User object, which is shown in the figure as a pointing to the User object.

insert image description here

When we use "==" to determine whether two objects are equal, we are actually judging whether the addresses stored in the two local variables are the same, in other words, we are judging whether the two local variables point to the same object.

After understanding these grammars of Java, let's look at the code at the beginning again.

Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2);
System.out.println(i3 == i4);

The first 4 lines of assignment statements will trigger the automatic boxing operation, that is, an Integer object will be created and assigned to the four variables i1, i2, i3, and i4. According to the explanation just now, although i1 and i2 store the same value, both of which are 56, they point to different Integer objects, so pass ""To determine whether it is the same, it will return false. Similarly, i3The i4 judgment statement also returns false.

However, the above analysis is still wrong. The answer is not two false, but one true and one false. Seeing this, you may be more puzzled. In fact, this is precisely because Integer uses the Flyweight pattern to reuse objects, which leads to such running results. When we create an Integer object by autoboxing, that is, calling valueOf(), if the value of the Integer object to be created is between -128 and 127, it will be returned directly from the IntegerCache class, otherwise it will be created by calling the new method . It is clearer to look at the code. The specific code of the valueOf() function of the Integer class is as follows:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

In fact, the IntegerCache here is equivalent to the factory class that generates Flyweight objects we talked about in the last lesson, but the name is not xxxFactory. Let's look at its specific code implementation. This class is an internal class of Integer, and you can also view the JDK source code by yourself.

/**
 * Cache to support the object identity semantics of autoboxing for values between
 * -128 and 127 (inclusive) as required by JLS.
 *
 * The cache is initialized on first usage.  The size of the cache
 * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
 * During VM initialization, java.lang.Integer.IntegerCache.high property
 * may be set and saved in the private system properties in the
 * sun.misc.VM class.
 */
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

Why does IntegerCache only cache integer values ​​between -128 and 127?

In the code implementation of IntegerCache, when this class is loaded, the cached flyweight objects will be created in one go. After all, there are too many integer values, and it is impossible for us to pre-create all the integer values ​​in the IntegerCache class. This will not only take up too much memory, but also make the loading of the IntegerCache class too long. Therefore, we can only choose to cache the most commonly used integer values ​​for most applications, which is the size of one byte (data between -128 and 127).

In fact, JDK also provides methods to allow us to customize the maximum value of the cache, there are the following two ways. If you analyze the JVM memory usage of the application and find that the data between -128 and 255 takes up more memory, you can use the following method to adjust the maximum value of the cache from 127 to 255. However, note here that the JDK does not provide a way to set the minimum value.

//方法一:
-Djava.lang.Integer.IntegerCache.high=255
//方法二:
-XX:AutoBoxCacheMax=255

Now, let's go back to the original question, because 56 is between -128 and 127, i1 and i2 will point to the same flyweight object, so i1i2 returns true. And 129 is greater than 127, it will not be cached, and a new object will be created every time, that is, i3 and i4 point to different Integer objects, so i3i4 returns false.

In fact, in addition to the Integer type, other wrapper types, such as Long, Short, Byte, etc., also use the Flyweight mode to cache data between -128 and 127. For example, the LongCache flyweight factory class and the valueOf() function code corresponding to the Long type are as follows:

private static class LongCache {
    private LongCache(){}

    static final Long cache[] = new Long[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Long(i - 128);
    }
}

public static Long valueOf(long l) {
    final int offset = 128;
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

In our usual development, for the following three ways of creating integer objects, we give priority to the latter two.

Integer a = new Integer(123);
Integer a = 123;
Integer a = Integer.valueOf(123);

The first creation method does not use IntegerCache, and the latter two creation methods can use IntegerCache cache to return shared objects to save memory. To give an extreme example, suppose the program needs to create 10,000 Integer objects between -128 and 127. Using the first creation method, we need to allocate memory space for 10,000 Integer objects; using the latter two creation methods, we only need to allocate memory space for up to 256 Integer objects.

Application of Flyweight Pattern in Java String

Just now we talked about the application of the flyweight pattern in the Java Integer class. Now, let’s look at the application of the flyweight pattern in the Java String class. Similarly, let's take a look at a piece of code first. What do you think is the output of this code?

String s1 = "小争哥";
String s2 = "小争哥";
String s3 = new String("小争哥");

System.out.println(s1 == s2);
System.out.println(s1 == s3);

The result of running the above code is: one true, one false. Similar to the design idea of ​​the Integer class, the String class uses the Flyweight pattern to reuse the same string constant (that is, the "little brother" in the code). The JVM will specially open up a storage area to store string constants. This storage area is called "string constant pool". The memory storage structure corresponding to the above code is as follows:

insert image description here

However, the design of the Flyweight mode of the String class is slightly different from that of the Integer class. The objects to be shared in the Integer class are created at one time when the class is loaded. However, for strings, we can't know in advance which string constants to share, so we can't create them in advance. We can only store them in the constant pool when a certain string constant is used for the first time. When it is used again later, it is enough to directly refer to what already exists in the constant pool, and there is no need to recreate it.

key review

Well, that's all for today's content. Let's summarize and review together, what you need to focus on.

In the implementation of Java Integer, integer objects between -128 and 127 will be created in advance and cached in the IntegerCache class. When we use autoboxing or valueOf() to create an integer object of this value range, the pre-created object of the IntegerCache class will be reused. The IntegerCache class here is the flyweight factory class, and the pre-created integer objects are flyweight objects.

In the implementation of the Java String class, the JVM opens up a storage area to store string constants. This storage area is called the string constant pool, similar to the IntegerCache in Integer. However, unlike IntegerCache, it does not create objects that need to be shared in advance, but creates and caches string constants as needed during the running of the program.

In addition, here I add to emphasize.

In fact, Flyweight mode is not friendly to JVM garbage collection. Because the enjoyment factory class has always saved the reference to the enjoyment object, which causes the enjoyment object to not be automatically recycled by the JVM garbage collection mechanism without any code use. Therefore, in some cases, if the object's life cycle is short and will not be used intensively, using the flyweight pattern may waste more memory. Therefore, unless it has been verified online that using the flyweight mode can really save memory a lot, otherwise, don’t overuse this mode. Introducing a complex design mode for a little bit of memory saving is not worth the candle.

class disscussion

IntegerCache can only cache pre-specified integer objects, so can we learn from the design idea of ​​String, instead of specifying which integer objects need to be cached in advance, but during the running of the program, when an integer object is used When it is created and placed in the IntegerCache, when it is used next time, it will be returned directly from the IntegerCache?

If you can do this, please reimplement the IntegerCache class according to this idea, and you can make it possible for an object to be recycled by the JVM garbage collection mechanism when there is no code in use.

Welcome to leave a message and share your thoughts with me. If you gain something, you are welcome to share this article with your friends.

Guess you like

Origin blog.csdn.net/fegus/article/details/130498813