This month, I started writing an article to analyze a poorly written micro-evaluation. After all, our programmers have been plagued by performance, and we all want to know the performance characteristics of the code we write, use, or criticize. When I accidentally write about the topic of performance, I often get emails like this: "This program I wrote shows that dynamic frosternation is faster than static blestification, contrary to your previous view!" Many of the so-called "profiling" programs that come with such emails, or the way they run, clearly show that they lack a basic understanding of the actual way in which the JVM executes bytecode. So before I start writing such an article (to be published in a future column), let's look at what's behind the JVM. Understanding dynamic compilation and optimization is the key to understanding how to differentiate micro-metrics (unfortunately, good micro-evaluations are rare).
A Brief History of dynamic compilation
The compilation process for a Java application differs from a static compilation language (for example, C or C + +). The static compiler directly converts the source code into machine code that can be executed directly on the target platform, and different hardware platforms require different compilers. The Java compiler converts Java source code into a portable JVM byte code, which refers to the JVM's "virtual machine directives." Unlike static compilers, Javac has little to do with optimizations--the optimizations that should be done by the compiler in a static compilation language, which is performed by the runtime in Java when the program executes.
The first generation of JVMs is entirely explained. Instead of compiling bytecode into machine code and executing machine code directly, the JVM interprets bytecode. Of course, this technology does not provide the best performance because the system spends more time executing the interpreter than it does on the program that needs to be run.
Just-in-time compilation
The explanation is appropriate for validating the implementation of the concept, but the early JVM gained a bad reputation quickly because it was too slow. The next-generation JVM uses the Just-in-time (JIT) compiler to improve execution speed. According to the strict definition, JIT based virtual machines convert all bytecode to machine code before execution, but do this in an inert way: JIT compiles this code path only when it determines that a code path will be executed (hence the name "Just-in-time compilation"). This technique allows the program to start faster because it does not require lengthy compilation stages before it can begin execution.
JIT technology looks promising, but it has some drawbacks. The JIT eliminates the burden of interpretation (at the expense of additional start-up costs), but for several reasons, the code's optimization level is still general. To avoid serious startup delays in Java applications, the JIT compiler must be very fast, which means it cannot spend a lot of time on optimization. As a result, the early JIT compilers were more conservative in making inline hypotheses (inlining assumption) because they did not know which class might be loaded later.
Although technically, a JIT based virtual machine compiles bytecode before executing bytecode, the term JIT is often used to represent any dynamic compilation process that converts bytecode to machine code-even if the process that interprets bytecode is counted.
HotSpot Dynamic Compilation
The HotSpot execution process combines compilation, profiling, and dynamic compilation. Instead of converting all of the bytecode to execute to machine code, it runs in an interpreter and compiles only "hot" code-the most frequently executed code. When HotSpot executes, the profiling data is collected to determine which code snippet executes frequently enough and is worth compiling. Compiling only the most frequently executed code has several performance advantages: not wasting time on compiling code that does not execute frequently, so that the compiler can spend more time optimizing the hot code path because it knows the time spent on it is worth it. Furthermore, by delaying compilation, the compiler can access profiling data and use that data to improve optimization decisions, such as whether or not to inline a method call.
To make things more complex, HotSpot provides two compilers: the client compiler and the server compiler. The default is the client compiler, and when you start the JVM, you can specify the-server switch and select the server compiler. The server compiler is optimized for maximum peak operation speed and is suitable for server applications that need to run for a long time. The optimization goal of the client compiler is to reduce the startup time and memory consumption of the application, which is much less complex than the server compiler and therefore requires less compilation time.
The HotSpot server compiler can perform various kinds of classes. It can perform standard optimizations common to many static compilers, such as code elevation (hoisting), public subexpression cleanup, cyclic expansion (unrolling), range detection cleanup, dead code cleanup, data flow analysis, and various optimization techniques that are not practical in statically compiled languages, such as the aggregation inline of a virtual method invocation.