Reflections on a Question in "In-depth Understanding of Java Virtual Machine"

1 Introduction

When I read "In-depth Understanding of Java Virtual Machine (3rd Edition)", I saw a code problem inside, and the book gave the answer to the problem. I thought about several variants of this topic expansion, and the results were different. In order to find the reasons for the differences, I had a deeper understanding.

2 - class initialization timing

2.1 - Original title

In the "In-depth understanding of the Java virtual machine (3rd edition)" chapter 7.2 "Class Loading Timing", the code listing 7-1 has such a piece of code:

public class SuperClass {
    static {
        System.out.println("SuperClass init!");
    }

    public static int VALUE = 1;
}

public class SubClass extends SuperClass {
    static {
        System.out.println("SubClass init!");
    }
}

public class Main {

    public static void main(String[] args) {
        System.out.println(SubClass.VALUE);
    }
}

The output is:

img_1

The book gives the answer to this result:

After the above code runs, only "SuperClass init!" will be output, but "SubClass init!" will not be output. For static fields, only the class that directly defines the field will be initialized, so referencing the static field defined in the parent class through its subclass will only trigger the initialization of the parent class but not the subclass. <sup>1</sup>

So when the main()method is called SubClass.VALUE, it is actually called SuperClass.VALUE. And SuperClassbefore it has not been loaded, the loading process is triggered, SuperClassand the staticstatic called during initialization.

2.2 - Variant 1

Here is a slight modification to the above code.

public class SuperClass {
    static {
        System.out.println("SuperClass init");
    }

    // public static int VALUE = 1;
    public final static int VALUE = 1; // 添加一个 final 修饰
}    

In the case where other codes remain unchanged, SuperClass.VALUEadd a finalmodifier, and the output is:

img_2

Unlike the original result, "SuperClass init!" and "SubClass init!" are not output.

For this result, I guessed at the beginning that because the VALUEfield is finalmodified and is a basic data type, the JVM has made some optimizations, SuperClass.VALUEinstead of directly referencing the value of this field.

Later, I looked at Main.classthe :

// Main.class
public class Main {
    public Main() {
    }

    public static void main(String[] args) {
        System.out.println(1);
    }
}

MainThe class SubClass.VALUEoptimizes . This is still somewhat different from the initial guess. The Mainclass is not optimized by the JVM at runtime, but directly optimized by the compiler.

In this case, what principle does the compiler optimize based on, we will expand in depth later, and continue to look at the next variant.

2.3 - Variant two

public class SuperClass {
    static {
        System.out.println("SuperClass init!");
    }

    // public static int VALUE = 1;
    // public final static int VALUE = 1;
    public final static Integer VALUE = 1; // 把VALUE改成Integer包装类
}   

This time, change the previous inttype to a VALUEwrapper class Integerand see the result of the operation.

img_3

This time the result again outputs "SuperClass init!". Indeed, the wrapper class is actually a finalmodified ordinary class, which cannot be optimized by the compiler like basic data types, so it needs to be called SubClass.VALUEand initialized SuperClass.

2.4 - Variant three

public class SuperClass {
    static {
        System.out.println("SuperClass init!");
    }

    // public static int VALUE = 1;
    // public final static int VALUE = 1;
    // public final static Integer VALUE = 1;
    public final static String VALUE = "1"; // 把VALUE改成String
}  

This time, change SubClass.VALUEfrom to and see the result of running:IntegerString

img_4

The result is now the same as the previous result of variant 1, which makes me a little puzzled. Are n'tString and are both wrapper classes, why can they not trigger initialization like basic data types? Is there any special treatment?IntegerSuperClassString

I still took a look at Main.classthe :

// Main.class
public class Main {
    public Main() {
    }

    public static void main(String[] args) {
        System.out.println("1");
    }
}

Indeed, as in the case of variant 1, the Stringcompiler VALUEdirectly optimizes the value of type directly at the compile stage.

3 - Compiler Optimization Techniques --- Conditional Constant Propagation

For the code execution results of variant 1 and variant 3 above, only VALUEthe value of , but not "SuperClass init!" is output. The primary reason is the compiler optimization technology .

Although the goal of the compiler is to translate program code into native machine code, the difficulty is not whether the machine code can be successfully translated. The quality of the output code optimization is the key to determining whether the compiler is good or not. On the official Wiki of OpenJDK, the HotSpot virtual machine design team has listed a relatively comprehensive list of optimization techniques used in just-in-time compilers. Address: https://wiki.openjdk.java.net/display/HotSpot/PerformanceTacticIndex. <sup>1</sup>

The official list of many compiler optimization techniques, of which conditional constant propagation (conditional constant propagation) is the cause of the output results of the above variants 1 and 3.

Constant propagation is one of the most widely used optimizations in modern compilers, and it is often applied to high-level intermediate representations (IRs). It solves the problem of statically detecting at runtime whether an expression always evaluates to a unique constant, if the procedure is called when it knows which variables will have constant values, and what those values ​​will be, the compiler can simplify constants at compile time . <sup>2</sup>

3.1 - Optimization constants

To put it simply, the compiler will find the constant in the code through a certain algorithm, and then directly replace the variable value pointing to it. E.g:

public class Main {
    public static final int a = 1; // 全局静态常量

    public static void main(String[] args) {
        final int b = 2; // 局部常量
        System.out.println(a);
        System.out.println(b);
    }
}

After the compiler compiles:

// Main.class
public class Main {
    public static final int a = 1;

    public static void main(String[] args) {
        int b = true;
        System.out.println(1);
        System.out.println(2);
    }
}

3.2 - Optimizing constant expressions

Even some constant expressions can be compiled directly in advance:

public class Main {
    public static void main(String[] args) {
        final int a = 3 * 4 + 5 - 6;
        int b = 10;
        if (a > b) {
            System.out.println(a);
        }
    }
}

After compilation:

// Main.class
public class Main {
    public static void main(String[] args) {
        int a = true;
        int b = 10;
        if (11 > b) {
            System.out.println(11);
        }

    }
}

3.3 - Optimized string concatenation

You can also compile the splicing of strings. There are often some questions on the Internet asking how many Stringobjects . In fact, the analysis at the level of the JVM virtual machine is not correct. Runtime memory pool.

public class Main {
    public static void main(String[] args) {
        final String str = "hel" + "lo";
        System.out.println(str);
        System.out.println("hello" == str);
    }
}

In the compiled source code, you can see that is strdirectly replaced by the "hello" string, and "hello" == stris true, so a String object is generated throughout the process.

// Main.class
public class Main {
    public static void main(String[] args) {
        String str = "hello";
        System.out.println("hello");
        System.out.println(true);
    }
}

Small expansion: In many places, it is said that multiple string splicing cannot be directly spliced ​​with "+", and use StringBuilder or the like. In fact, even using "+" will be optimized into StringBuilder by the compiler. If you are interested, you can try it yourself.

3.4 - Risks from Compiler Conditional Constant Propagation

Although the compiler optimizes the code to improve the efficiency of the runtime, it also brings certain risks

3.4.1 - Constant reflection failure

Although some finaldecorated fields will be considered constant by the compiler to optimize, but Java has reflection mechanism, through some strange tricks can change these values. However, due to optimization by the compiler, the modified value may not take effect as expected. Such as:

public class Main {
    public static final String VALUE = "A";

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Class<Main> mainClass = Main.class;
        Field value = mainClass.getField("VALUE");
        value.setAccessible(true);

        // 去除A的final修饰符
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(value, value.getModifiers() & ~Modifier.FINAL);

        value.set(null, "B");
        System.out.println(VALUE); // 实际还是输出 "A"
    }
}  

Although this code has changed VALUEthe , the compiler has already System.out.println(VALUE);replaced it with when compiling System.out.println("A");, and the final running result will be different from what is expected.

3.4.2 - Partial compilation

If the constant and the object it refers to are not in the same file, and only the file where the constant is located is recompiled after the constant is modified, the unrecompiled file will use the old value. Such as:

// Constant.java
public class Constant {
    public final static String VALUE = "A";
}

// Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println(Constant.VALUE);
    }
}

If Constant.VALUEthe value of is changed to "B" and then the javac Constant.javaConstant.java file is compiled separately, the output value Maininside will still be "A".

4 - Constants, Static Constant Pool, Dynamic Constant Pool

4.1 - Constants

A constant is a quantity whose value remains the same throughout the run of the program. In Java development, it usually refers to the finalmodified variable. But the definition of "constant" will be different from the point of view of the virtual machine.

In a virtual machine, constants are stored in a constant pool, and two types of constants are stored in the constant pool: literals and symbolic references. Literals are closer to constant concepts at the Java language level, such as text strings, constant values ​​declared as final, etc. <sup>1</sup> and symbolic references belong to the concept of compilation principle, mainly including class, field, method information, etc., and will not be described here.

4.2 - Static constant pool

The (static) constant pool can be compared to the resource warehouse in the Class file. It is the data most associated with other projects in the Class file structure, and is usually one of the data items that occupy the largest space in the Class file. In addition, it is still in the Class file. The first occurrence of a table-type data item.

The types of data items stored in the (static) constant pool are as follows:

img.png

img_1.png <sup>1</sup>

After the static constant pool is compiled, it is written in the class file. You can directly view the bytecode to observe its structure, such as the following code:

public class Main {
    final static String A = "A";
    final static int B = 1;
    final static Integer C = 2;

    public static void main(String[] args) {
        System.out.println(A);
        System.out.println(B);
        System.out.println(C);
    }
}

After compiling, use the javap -verbose Main.classcommand to view the decompiled bytecode:

img.jpg

It can be found that the Stringand inttype data in the code are stored in the static constant pool, Integerbut there is no. Because the former corresponds to the "CONSTANT_String_info" and "CONSTANT_Integer_info" types in the constant pool, while the latter is equivalent to an ordinary object, only the object information is stored.

This explains why the results of variant 1, variant 3 and variant 2 above are different.

4.3 - Dynamic Constant Pool

Another important feature of the runtime constant pool (dynamic constant pool) compared to the Class file constant pool (static constant pool) is that it is dynamic. The Java language does not require constants to be generated only at compile time, that is, not preset. Only the content of the constant pool in the Class file can enter the runtime constant pool of the method area, and new constants can also be put into the pool during runtime. This feature is used more by developers than the intern() method of the String class. .

<div id="refer-anchor"></div>

5 Conclusion

"In-depth Understanding of Java Virtual Machine" is indeed a very good book. I have read it several times, and each time I have new harvests. This time, it was because of my "accidental mistakes" when I was typing the source code. other gains.

refer to


Original address: Reflections on a question in "In-depth Understanding of Java Virtual Machines"

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324240668&siteId=291194637