What is Java Source Code Compiled into?
The JAVA is a platform-independent programming language. The JAVA language does not follow one-step compilation; rather, it follows the process of two-step execution of code. The JAVA code gets executed in the following steps:
- Step 1: Conversion to compiled code through an OS-independent compiler.
- Step 2: Execution of compiled code in a virtual machine (JVM) that is custom-built for every operating system.
When we use the javac compiler, the JAVA source code is compiled into bytecode. The bytecode is then saved on the disk with the .class file extension.
Then, when we are about to run the program, the just-in-time (JIT) compiler would turn the bytecode into instructions such that it could be sent directly to a computer's processor (CPU).
Is Java Interpreted or Compiled?
A very common question among JAVA developers is Whether JAVA is interpreted or compiled?
Now, before we deep dive into this question, let us learn briefly about what is interpreted and what is compiled?
-
What is interpreted code?
In an interpreted code, the target machine does not directly translate the source code. Instead, a different program, aka the interpreter, reads and executes the instructions.
-
What is compiled code?
Compilation is creating an executable program from code written in a compiled programming language. Compiling allows the computer to parse the program and translate the high-level codes into low-level assembly language.
-
The Java is both interpreted and compiled.
Java is both a compiled and an interpreted language. Its source code is first compiled into binary bytecode. After this, the bytecode runs on the Java Virtual Machine (JVM), which is usually a software-based interpreter.
Java is renowned for its platform independence, meaning code written on one platform can seamlessly run on another without modifications. This versatility stems from Java's compilation process, which generates bytecode instead of machine code. Unlike traditional interpretation, Java bytecode is not executed line by line. Instead, it's interpreted by the Java Virtual Machine (JVM), translating it into machine instructions. This combination of compilation and interpretation makes Java both compiled and interpreted, ensuring its portability across different hardware and operating systems.
Simple Java Program Working
In this section, we will go through a simple java code example to learn the compilation and execution of a Java program in detail.
Compilation of the JAVA program
In this step, the .java file, which is the source file, will first pass through the compiler. Once the file passes the compiler, it will be encoded into a machine-independent encoding known as bytecode. After the encoding, a separate .class file stores each class's content.
Following are the steps followed by the compiler when it converts the source code into bytecode:
-
Step 1: The compiler reads a set of *.java source files and stores the resulting token sequence by mapping it into Abstract Syntax Tree (AST) Nodes. This step is known as Parsing.
-
Step 2: The compiler will enter the symbols for the definitions in the symbol table. This step is known as Enter.
A Symbol table is an important data structure used in a compiler. The symbol table stores information about objects, classes, variable names, interfaces, function names,, etc. It is used in both the analysis and synthesis phases.
Functions of symbol table:
-
It is used to store the names of all entities in a structured form in one place.
-
It verify if a variable has been declared.
-
It determine the scope of a name.
-
It implements type checking by verifying that assignments and expressions in the source code are semantically correct.
-
-
Step 3: Upon request, the compiler runs process annotations , i.e. the compiler will analyze the source code for user-defined annotations and handle them by producing compiler errors, compiler warnings, emitting source code, byte code, etc. This step is known as Process annotations.
-
Step 4: The compiler will attribute the syntax tree, including name resolution, performing constant folding, and doing type checking. This step is known as Attribute.
-
Step 5: The previous step is put into dataflow analysis. This dataflow analysis is done on the tree and checks for reachability and assignments. This step is known as Flow.
-
Step 6: The compiler will rewrite the Abstract Syntax Tree and does translate a few syntactic sugars. This step is known as Desugar.
-
Step 7: The .Class file gets generated. This step is known as Generate.
Execution of the JAVA Program
The class files can run on any system since the class files that the compiler generates are not dependent on the OS or machine.
The main class in java contains the main method. Thus, to trigger the main method, we need to run the main class. To run, we pass the main class file to the Java virtual machine (JVM), which then passes through three main stages before executing the final code.
The three main stages are the following:
- Stage 1: ClassLoader
- Stage 2: Bytecode Verifier
- Stage 3: Just-In-Time Compiler
Stage 1: Class Loader
In this stage, we load the class into memory. The main class .class file is passed to the JVM. The class loader is used to load all the other classes that we are referencing in the program.
Note: A class loader** is an object responsible for loading classes. The class ClassLoader is an abstract class.
When a Java program starts, the JVM loads the main class by calling its public static void main(String[] args) method. As the main class executes, any referenced classes are loaded as needed. The class loader creates a namespace for these loaded classes within the JVM. Example:
- The className represents the name of the class to be loaded.
- The resolve is used as a flag to decide whether any reference class should be loaded or not
There are two types of class loaders:
- primordial
- non-primordial
The primordial class loader is used to bootstrap the class loading process. It is the default class loader, and all Java Virtual Machines (JVM) are embedded with primordial class loaders.
Note: The Bootstrap Classloader is a critical component of the JVM responsible for initiating operations. It's not a Java class but rather machine code, designed to load the initial pure Java ClassLoader.
We can also customize the class loading process by writing user-defined class loaders, which are known as non-primordial class loaders.
Note: If a noprimordialal class loader is defined in the program, then it is preferred over the defaulprimordialal class loader to load the classes.
Stage 2: Bytecode Verifier
The bytecode of a class needs to be inspected once the class loader loads it. ** bytecode verifier does this verification**. The bytecode verifier keeps checking that the instructions do not perform any damaging action.
The bytecode verifier carries out the following checks:
The bytecode verifier checks whether the variables' initialization is done properly before they are used in the program.
- The bytecode verifier checks if the method calls made in the program match that object reference type.
- The bytecode verifier ensures that the rules associated with accessing private data and methods are followed properly.
- The bytecode verifier checks the local variable accesses that fall within the runtime stack.
- The bytecode verifier checks that the overflow condition does not occur in the run-time stack. If any of the above checks fail, the bytecode verifier stops loading the class.
Stage 3: Just-In-Time Compiler
This is the third and final stage that a java program encounters. In this stage, the loaded bytecode is converted into machine code.
With a JIT compiler, hardware can directly execute native code instead of the JVM repeatedly interpreting bytecode, avoiding the overhead of translation. This often boosts execution speed, especially for frequently executed methods. The following diagram illustrates the java code execution:
Java program is independent of the target operating system because of the two-step execution process described above, a. hence, the execution time is much longer than that of a similar program written in a compiled platform-dependent program.
Example
In this section, we will execute a simple Java program. Let us consider the following Java program:
Output:
Stepwise compilation and execution of java program:
- Step 1: We will create a simple file and will save it with the .java extension.
- Step 2: We will open out the file and put the above code in it.
- Step 3: We will compile our code using:
- Step 4: Finally, we will execute the program using:
Conclusion
- The Java is both interpreted and compiled.
- Java is a platform-independent language.
- The Java program does not generate machine code once the source file is compiled.
- The Java program does not execute the code instructions line by line thus it does not interpret the source file.
- The Java compiler compiles the Java source code into a binary byte code.