Application of meta pattern in Java Integer
Let's take a look at the following code. You can think about what this code will output first.
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 i1 and i2 values are 56, i3 and i4 values are 129, i1 and i2 values are equal, and i3 and i4 values are equal, so the output result should be two truths. This analysis is wrong, mainly because you are not familiar with Java syntax. To correctly analyze the above code, we need to clarify the following two problems:
- How to determine whether two Java objects are equal (that is, the meaning of the "= =" operator in the code)?
- What are Autoboxing and Unboxing?
You know, Java provides corresponding wrapper types for basic data types. As follows:
The so-called automatic packing is to automatically convert the basic data type to the wrapper type. The so-called automatic unpacking is to automatically convert the wrapper type to the basic data type. As follows:
Integer i = 56; //Automatic packing int j = i; //Automatic unpacking
The value 56 is the basic data type int. when copied to the wrapper type variable, the automatic boxing operation is triggered, an Integer type object is created, and assigned to the variable i. Its bottom layer is equivalent to:
Integer i = 59;The underlying layer performs: Integer i = Integer.valueOf(59);
Conversely, when the variable i of the wrapper type is assigned to the basic data type variable j, the automatic unpacking operation is triggered to take out the data in i and assign it to j. The bottom layer is equivalent to executing the following statement:
int j = i; The underlying layer performs: int j = i.intValue();
After figuring out the automatic packing and unpacking, let's see how to determine whether the two objects are equal? However, before doing so, we need to find out how Java objects are stored in memory. Let's illustrate it with the following example.
User a = new User(123, 23); // id=123, age=23
For this statement, the memory storage structure is shown below. The value stored by a is the memory address of the User object. In the figure, a points to the User object.
When we judge whether two objects are equal by "= =", we are actually judging whether the stored addresses of two local variables are the same. In other words, we are judging whether two local variables point to the same object.
After understanding these Java grammars, let's take a look at the beginning of the code.
Integer i1 = 56; Integer i2 = 56; Integer i3 = 129; Integer i4 = 129; System.out.println(i1 == i2); System.out.println(i3 == i4);
The first four 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 the stored values of i1 and i2 are the same, both are 56, they point to different Integer objects. Therefore, when determining whether they are the same through = =, false will be returned. Similarly, the i3==i4 decision statement will also return false.
However, the above analysis is still wrong. The answer is not two false, but one true and one false. This is because Integer uses the meta pattern to reuse objects, which leads to such a running result. When we create an Integer object through automatic boxing, that is, calling valueOf(), the value of the Integer object to be created is between - 128 and 127, which will be returned directly from the IntergerCache class. Otherwise, we call the new method to create it. 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); }
The InterCache here is equivalent to generating the factory class of meta pattern.
** * Cache to support the object identity semantics of autoboxing for values betw * -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 meta objects will be created at one time. After all, there are too many integer values. It is impossible to pre create all integer values in the InterCache class, which not only occupies too much memory, but also takes too long to load the IntergerCache class. Therefore, we can only choose to cache the most commonly used integer value for most applications, that is, the size of a byte (- data between 128 and 127)
Now, let's go back to the initial problem. Because 56 is between - 128 and 127, i1 and i2 will point to the same meta object, so i1==i2 returns true. If 129 is greater than 127, it will not be cached. Each time, a new object will be created, that is, i3 and i4 point to different Integer objects, so i3==i4 returns false.
In fact, in addition to Integer type, other wrapper types, such as Long, Short, Byte, etc., also use meta mode to cache data between - 128 and 127. For example, the LongCache meta factory class and valueOf() function code corresponding to 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 methods 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, while the latter two creation methods can use IntegerCache cache to return shared objects to save memory.
Application of meta pattern in Java String
Similarly, let's look at a piece of code first,
String s1 = "Little brother Zheng"; String s2 = "Little brother Zheng"; String s3 = new String("Little brother Zheng"); System.out.println(s1 == s2); System.out.println(s1 == s3);
The running result of the above code is: a true and a false. Similar to the design idea of the Integer class, the String class uses the shared meta pattern to reuse the same String constant (that is, the "little brother" in the code). The JVM will open up a special storage area to store String constants, which is called "String constant pool". The memory storage structure corresponding to the above code is as follows:
However, the design of the meta sharing mode of the String class is a little different from that of the Interger 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 which String constants to share in advance, so we can't create a number in advance. We can only store a String constant in the constant value when it is used for the first time. When it is reused later, we can directly reference the existing ones in the constant pool, so we don't need to recreate it.
summary
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 auto boxing or valueOf() to create integer objects of this value range, we will reuse the objects created in advance by the IntegerCache class. The IntegerCache class here is the meta factory class, and the integer object created in advance is the meta object.
In the implementation of Java String class, the JVM opens up a storage area for storing string constants. This storage area is called string constant pool, which is similar to IntegerCache in Integer. However, unlike IntegerCache, it does not create objects to be shared in advance, but creates and caches string constants as needed during the running of the program.
However, meta mode is not friendly to JVM garbage collection. Because the meta factory class always saves the reference to the meta object, the meta object will not be automatically recycled by the JVM garbage collection mechanism without any code. Therefore, in some cases, if the object's life cycle is very short and will not be used intensively, using the shared meta mode may waste more memory. Therefore, unless it is verified online that using the shared meta mode can really save memory, don't overuse this mode. It's not worth the loss to introduce a complex design mode for a little memory saving