This is the 24th day of my participation in the August Text Challenge.More challenges in August

preface

Compilation optimization content is still a lot, of course, the main content is focused on the back end of the compilation above, in order to control the length of the length so I choose to split into two parts to explain, we usually write code and the actual runtime code effect is completely different, it is necessary to understand the details of compilation optimization.

An overview of the

  1. Learn the basic compilation process of Javac and basic processing details
  2. Understand the basics of front-end optimization: syntactic sugar and generic implementations
  3. Understand what is compiled at the front and back ends and some of what is compiled at the back ends.

Javac compilation process

The project code for Javac is not part of the Java SE API, and since the JDK9 version is separate from the modularity, the book uses the JDK9 version to explain the compilation process for Javac.

The preparatory work

Jdk9 (package) location: JDK_SRC_HOM E/src/jdk.com iler p/share/classes/com/sun/tools/javac

If you need to build a Javac project, just create a new project and copy the contents of the following path under the project.

J D K _ SR C _ H O M E / l a n gt o o l s / s r c / s h a r e / c l a s s e s / c o m / s u n / *

Once the copy is complete, a project supporting the Javac command is set up

Compilation steps for javac

The steps for compiling javac are as follows, but note that the annotation processor in THE JDK5 version is included:

  1. Preparation: Initializes the plug-in annotation handler

  2. The process of parsing and populating symbol tables

    1. Lexical analysis

    2. Fill symbol table

  3. Plug-in annotation processor processing:

    1. The execution phase of the plug-in annotation processor
  4. Analysis and bytecode generation (parsing is a common part of IDE)

    1. Annotation checking (data analysis, constant folding optimization)

    2. Data flow and data analysis (contextual semantic analysis check)

    3. Solution sugar (triggered by DESAGRC method)

    4. Bytecode generation

Here’s a diagram of the compilation process from the book, showing that the order is not fixed, but can be changed:

The front-end optimization

Annotation processor

The annotation handler step is a new addition to JDK5. In the Javac source code, the annotation handler is initialized in the initPorcessAnnotations() method and executed in the processAnnotations() method. This method determines if there are any new annotation handlers that need to be executed, and if so, C o m. S u n. T o o l s. J a v a c p r o c s s I n g D o P r o c e s I n g() to generate a new JavaComp iler object that handles the subsequent steps of compilation.

Syntactic sugar

In the process of upgrading Java, we introduced a lot of syntax sugar, such as jdK5’s enhanced for loops and generics, JDK7’s generic diamond tags and try-catch-resource, jdK8’s lambada expressions, and so on, which made the JDK easy to use. Here are a few key upgrades to describe:

The generic

The inspiration for generics came from Martin Odersky, the author of Scala, pizza’s successor language. When he invented generics, he was immediately invited to develop generics in Java. Martin Odersky was limited by Java’s syntax and backward compatibility features. The result was something more like C# generics (ironically), with the end result that Java is still in the habit of being lazy about the technology.

Anyway, generics are probably familiar to all Java developers and won’t be covered here. There are two common ways to implement generics:

  • The generalization type remains unchanged, and the new generalization type is added in parallel

  • Existing types are genericized without adding any generic types.

Java uses the second approach for no other reason than laziness. At the time, it would have been better to choose the first approach and there would have been more solutions if there had been more time for discussion. Here’s a brief overview of the basic characteristics of generics and what needs to be implemented:

Type erasure

First, Java introduced a mechanism for type erasers. Java generics were initially called bare types (parent types). Bare types can be thought of as pre-JDK5 types without Angle brackets.

  • The virtual machine does the real construction
  • Restore at compile time, and type coercion occurs when an element is accessed.

Yes, the Java implementation also uses the second way, strong implementation is much simpler than the first way, but it also brings the following problems:

  • Support for primitive types is cumbersome, and Java’s use of automatic type conversions instead directly leads to inefficient automatic unboxing
  • The type of the generic cannot be read at runtime, so a Java generics is only a “fake” generics.

The erasure mechanism of generics determines that Java’s generics support is more of a compiler service.

Note: 1. Erasure is only code bytecode erasure. 2. Retain the metadata information before erasure.

Generalized properties

The Sinature property: : stores the special signature of the method at the bytecode level. The property stores the parameterized type information instead of the original type.

ValueType support: value types are also called valuetypes, which are types that can define underlying data types.

The implementation of conditional compilation

Conditional compilation can be simply interpreted as an if statement. Java does not inherently support conditional compilation, but C and C++ do.

Conditional compilation is implemented in Java, which is also a syntactic sugar of Java language. Depending on the Boolean constant value, the compiler will remove unjustified code blocks from branches. This is done in the compiler unsyntactic sugar phase (com.sun.tools.javac.p.lower class).

The Shenandoah collector is not supported by the JDK and can only be used on openJDK.

Through the above content learning and understanding, it can be found that the front-end compilation function is relatively small, can be regarded as a part of the syntax sugar, back-end optimization is not so simple, let’s look at how back-end optimization is implemented.

The back-end optimization

Just-in-time compiler

Even though the importance of compilers goes without saying, Hotspot, which is still the mainstream compiler, shows the importance of just-in-time compilers. One of the important optimizations in Hotspot is that even with compilers, before we can understand just-in-time compilers, we need to clarify the following questions:

  • Why do interpreters and just-in-time compilers coexist

  • Why multiple compilers

  • When to use the interpreter and when to use the just-in-time compiler

  • What code is native and how is it compiled

  • How will the results be observed externally

By solving the above questions, we can get to the heart of just-in-time compilation.

Just-in-time compilation: method-oriented rather than local code-oriented, this approach to bytecode sequence number substitution is called on-stack substitution, and methods are implicitly replaced by the compiler at frame time.

Why do interpreters and compilers coexist?

Not all real-time compilers use the coexistence mode of interpreter and compiler, but there are several mainstream products in this coexistence mode, what is its role? First, it acts as an escape hatch, keeping things working in normal situations, but using the interpreter as a “backstop” when the compiler is overwhelmed or native code becomes too much, ensuring that the code will always work in any situation. The so-called men and women together, work not tired.

Why are there more than one?

There are two compilers under Hotspot’s compiler:

  • C1: Client-side compiler: efficient, very fast, but of mediocre quality

  • C2: Server-side compiler: high quality but less efficient

Why there should be more than one compiler is another historical topic. In the early days, the interpreter was efficient based on server resources and user-specified matching of front-end compiler processing, so multiple is understandable.

Layered compilation

We no longer need to understand how it worked before, but rather the hierarchical compilation approach that has been fully implemented since JDK7:

  1. Pure interpreter mode: Layer 1

  2. The client compiler executes, enabling partial monitoring: layer 2

  3. The client compiler executes, enabling full monitoring: layer 3

  4. Server-side compilation to native code: Layer 4

Of course, the above steps are not completely fixed, and the order can be adjusted according to the actual situation. Here is a picture given in the book:

Hot code detection

There are two methods of hotspot detection: Sample Based Hot Spot Code Detection and Counter Based Hot Spot Code Detection, The second Counter based HotSpot detection method is used in the HotSpot virtual machine. For HotSpot counting, HotSpot HotSpot has two types of counters for each method: The Method Invocation Counter and the Back Edge Counter. “Back edge” means to jump back at the loop boundary).

Hotspot code detection is another of Hotspot’s “soul” back-end optimizations. Hotspot code is also referred to as multi-call code or multi-execution loop body code, but how does Hotspot tell? How do I get how many times a method is executed? And what does “long enough” mean?

To answer the question of how many times it is executed, hotspot uses two counters to do this: a method call counter and a backside counter.

Long enough, it’s a little more complicated, and method calls and return counts are judged differently. Here is a simple list to illustrate the criteria for triggering a method caller hotspot code:

Method call counter

Method caller: client-side compiler15000Second, the server compiler10000Subcondition: back edge + method call >= threshold above Note: number of calls in the time range. Statistical method: half decline period.Copy the code

Back edge counter

A loopback counter is a count of the number of times a loop is executed. It does not count the number of times a loop is executed. Instead, it uses the following formula:

Client mode (default 13995) : method call counter threshold (-xx: C o m p I l e T h r e s h o l d) multiplied by o SR ratio (-x XX: O n St a c k R e p l a c e p e R c e n t a ge) -x X: OnStackRep lacePercentage The default value is 933

Server mode (default 10700) ** : ** Method call counter threshold (-xx: C o m p I l e T h r e s h o l d) multiplied by (o SR ratio (-x XX: O n St a c k R e p l a c e p e R c e n t a ge) minus the interpreter monitoring ratio (- X X: InterpreterProfilePercentage) difference) divided by 100. -xx :OnStack ReplacePercentage The default value is 140. -x X: I nt er P r E t E r P R O F I L E Per C E N tage The default value is 3 3.

When the backside method triggers a threshold, it triggers an operation called “on-stack replacement.” And the loopback counter has no concept of a half-decay period. It is triggered when the absolute value condition is reached, and if the number keeps growing to the upper limit of the counter and overflows, the loopback counter resets and incidentally returns the method counter to 0. Finally, when the loopback count reaches the threshold, it will lower the current loopback counter slightly so that the next code will continue to execute the loop (otherwise it will not make sense to replace the stack and execute the loop).

Comparison of structure drawings:

According to the above description, let’s look at the calculation logical structure diagram of the two counters:

The method call

Back edge counter

Front and back end compilation overview

conclusion

In this section we cover the low-level execution of JavAC instructions, as well as front-end and back-end optimizations. Front-end optimizations are mainly syntactic sugar optimizations for Java and an important optimization annotation generator. In subsequent articles we covered some of the ways in which back-end compilation optimizations, even compilers, and hot code detection, and in just-in-time compilation we covered layered compilation. Finally, we illustrated the compilation with a structure diagram.

Write in the last

This article covers back-end compilation, and the next section will cover another part of it.