JVM – 1 [ Byte Code ]

The computer cannot run directly java code (.java), it must be compiled into bytecode (.class), then it can be run by the java virtual machine.

JVM not only supports Java, but also many programming languages, such as Groovy, Scala, Koltin …

What You Need

  • About 10 minutes
  • A favorite text editor or IDE
  • Java 8 or later

Class File

The class file is essentially a binary stream based on 8-bit bytes, the jvm parses it according to its specific rules to obtain relevant information.

Below is a simple java file example :

 1public class ByteCodeTest {
 2    public static void main(String[] args) {
 3        int a = 1;
 4        int b = 2;
 5        int c = add(a, b);
 6
 7        System.out.println(c);
 8    }
 9
10    private static int add(int a, int b) {
11        return a + b;
12    }
13}

We can use command javac to obtain its .class file :

ovo@ovo:~/github/BlogTests/java.jvm.bytecode$ javac ByteCodeTest.java
ovo@ovo:~/github/BlogTests/java.jvm.bytecode$ ls
ByteCodeTest.class  ByteCodeTest.java

Open the generated class file in a hex editor or make a hexdump (xxd .class), the content is as follows :

The 4 bytes at the beginning of the file (CAFE BABE) are called magic number.

These 4 bytes are the identification of the bytecode file.

Only the class file starting with CAFE BABE can be accepted by the virtual machine.

The next 2 bytes are the major version number of the JDK that the class file is using.

Below is all the major version number of jdks :

JDKMAJOR VERSION NUMBER
Java SE 1761 (0x3D hex)
Java SE 1660 (0x3C hex)
Java SE 1559 (0x3B hex)
Java SE 1357 (0x39 hex)
Java SE 1256 (0x38 hex)
Java SE 1155 (0x37 hex)
Java SE 1054 (0x36 hex)
Java SE 953 (0x35 hex)
Java SE 852 (0x34 hex)
Java SE 751 (0x33 hex)
Java SE 650 (0x32 hex)
Java SE 549 (0x31 hex)
JDK 1.448 (0x30 hex)
JDK 1.347 (0x2F hex)
JDK 1.246 (0x2E hex)
JDK 1.145 (0x2D hex)

Let us now decompile this class file and continue to look at it.

We use the built-in Java decompilation tool javap :

javap -v -p -l ByteCodeTest.class

Below is the decompiled output :

 1Classfile /home/yan/github/BlogTests/java.jvm.bytecode/ByteCodeTest.class
 2  Last modified May 23, 2025; size 482 bytes
 3  SHA-256 checksum 238817dfe5cc08380cb7936bc791b4be3afa105490471cb51d20330bbb722b03
 4  Compiled from "ByteCodeTest.java"
 5public class ByteCodeTest
 6  minor version: 0
 7  major version: 65
 8  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
 9  this_class: #8                          // ByteCodeTest
10  super_class: #2                         // java/lang/Object
11  interfaces: 0, fields: 0, methods: 3, attributes: 1
12Constant pool:
13   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
14   #2 = Class              #4             // java/lang/Object
15   #3 = NameAndType        #5:#6          // "<init>":()V
16   #4 = Utf8               java/lang/Object
17   #5 = Utf8               <init>
18   #6 = Utf8               ()V
19   #7 = Methodref          #8.#9          // ByteCodeTest.add:(II)I
20   #8 = Class              #10            // ByteCodeTest
21   #9 = NameAndType        #11:#12        // add:(II)I
22  #10 = Utf8               ByteCodeTest
23  #11 = Utf8               add
24  #12 = Utf8               (II)I
25  #13 = Fieldref           #14.#15        // java/lang/System.out:Ljava/io/PrintStream;
26  #14 = Class              #16            // java/lang/System
27  #15 = NameAndType        #17:#18        // out:Ljava/io/PrintStream;
28  #16 = Utf8               java/lang/System
29  #17 = Utf8               out
30  #18 = Utf8               Ljava/io/PrintStream;
31  #19 = Methodref          #20.#21        // java/io/PrintStream.println:(I)V
32  #20 = Class              #22            // java/io/PrintStream
33  #21 = NameAndType        #23:#24        // println:(I)V
34  #22 = Utf8               java/io/PrintStream
35  #23 = Utf8               println
36  #24 = Utf8               (I)V
37  #25 = Utf8               Code
38  #26 = Utf8               LineNumberTable
39  #27 = Utf8               main
40  #28 = Utf8               ([Ljava/lang/String;)V
41  #29 = Utf8               SourceFile
42  #30 = Utf8               ByteCodeTest.java
43{
44  public ByteCodeTest();
45    descriptor: ()V
46    flags: (0x0001) ACC_PUBLIC
47    Code:
48      stack=1, locals=1, args_size=1
49         0: aload_0
50         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
51         4: return
52      LineNumberTable:
53        line 1: 0
54
55  public static void main(java.lang.String[]);
56    descriptor: ([Ljava/lang/String;)V
57    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
58    Code:
59      stack=2, locals=4, args_size=1
60         0: iconst_1
61         1: istore_1
62         2: iconst_2
63         3: istore_2
64         4: iload_1
65         5: iload_2
66         6: invokestatic  #7                  // Method add:(II)I
67         9: istore_3
68        10: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
69        13: iload_3
70        14: invokevirtual #19                 // Method java/io/PrintStream.println:(I)V
71        17: return
72      LineNumberTable:
73        line 3: 0
74        line 4: 2
75        line 5: 4
76        line 7: 10
77        line 8: 17
78
79  private static int add(int, int);
80    descriptor: (II)I
81    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
82    Code:
83      stack=2, locals=2, args_size=2
84         0: iload_0
85         1: iload_1
86         2: iadd
87         3: ireturn
88      LineNumberTable:
89        line 11: 0
90}
91SourceFile: "ByteCodeTest.java"

Global Information

The first section (line 1 – line 11) represents the global information of the class file, it includes below lines :

Line 1 – The current location of the Class file

Line 2 – The last modification time and the file size

Line 3 – The MD5 value

Line 4 – From which file it was compiled

Line 5 – The fully qualified name of the class

Line 6 – The jdk minor version number

Line 7 – The jdk major version number

Line 8 is the access flags :

NAMEVALUEDESCRIPTION
ACC_PUBLIC0x0001if it is public, may be accessed from outside its package
ACC_FINAL0x0010if it is final, no subclasses allowed
ACC_SUPER0x0020Treat superclass methods specially when invoked by the invokespecial instruction, it was introduced to correct a problem with the invocation of super methods
ACC_INTERFACE0x0200if it is an interface, not a class, if this flag is not set, then it indicates that it is a class, not an interface, otherwise ACC_ABSTRACT flag must also be set, and it must not have ACC_FINAL, ACC_SUPER or ACC_ENUM flags set
ACC_ABSTRACT0x0400if it is abstract, may not be instantiated
ACC_SYNTHETIC0x1000if it was generated by a compiler and does not appear in source code
ACC_ANNOTATION0x2000if it is annotation, if this flag is set, the ACC_INTERFACE flag must be set as well and it cannot have both ACC_FINAL and ACC_ABSTRACT flags set
ACC_ENUM0x4000if it is an enum

Line 9 is this_class : it is the class name and its value must be a valid index into the constant_pool table.

Line 10 is super_class : it is the super class of the class file and its value must be a valid index into the constant_pool table. If its value is zero, then this class file must represent the class Object, the only class or interface without a direct superclass.

Line 11 contains the count of interfaces, fields, methods and attributes of the class file.

Constant Pool

The second section (line 13 – line 42) is constant pool.

There are two main types of constants: literal (Literal) and symbolic reference (Symbolic References).

Literals are similar to constant concepts in java, such as text strings, final constants etc.

Symbolic references belong to the concept of compilation principles, including the following :

  • Fully Qualified Name of classes and interfaces;
  • Field name and descriptor;
  • Method name and descriptor.

JVM performs dynamic linking when loading the Class file, which means that these field and method symbol references can only obtain the real memory entry address after the runtime conversion.

When the virtual machine is running, it needs to obtain the corresponding symbol reference from the constant pool, and then parse and translate it into the specific memory address when the class is created or run.

Let us have a look at the first constant in the pool.

It is a method reference whose value points to constant 2 and 3 which are separated by a comma.

And so on for the constant 2 (points to constant 4) and 3 (points to constant 5 and 6).

Finally, it obtains the comment content on the right side of the first constant :

13   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
14   #2 = Class              #4             // java/lang/Object
15   #3 = NameAndType        #5:#6          // "<init>":()V
16   #4 = Utf8               java/lang/Object
17   #5 = Utf8               <init>
18   #6 = Utf8               ()V

It is the declaration of the instance constructor of this class.

Since this class does not override any constructor, the constructor of the parent class (Object) is called.

The default return value of this method is V, which is void, with no return value.

V is also called bytecode type.

Below is a list of bytecode types :

CHARACTERDESCRIPTION
Bbyte
Cchar
Ddouble
Ffloat
Iint
Jlong
Sshort
Zboolean
Vvoid
L<classname>;Reference, an instance of class <classname>, e.g. Ljava/lang/object;
[Array, for instance, to define a java.lang.String[][], it will be [[Ljava/lang/String;

Methods

Next section (line 43 – line 90) is the description of the methods inside the class.

For instance, below is the source code of method add:

private static int add(int a, int b) {
    return a + b;
}

Below is bytecode description of this method add:

79  private static int add(int, int);
80    descriptor: (II)I
81    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
82    Code:
83      stack=2, locals=2, args_size=2
84         0: iload_0
85         1: iload_1
86         2: iadd
87         3: ireturn
88      LineNumberTable:
89        line 11: 0

Line 79 is the signature of this method including the access flag and return type.

Line 80 is method descriptors which represents the parameters that the method takes and the value that it returns.

MethodDescriptor: ( ParameterDescriptor* ) ReturnDescriptor

For this add method, it has two integer parameters and it returns an integer value, so its method descriptior is (II)I.

Line 81 is the access flags of this method.

From line 82, it is the Code Attribute block.

It includes below fields :

FIELDDESCRIPTION
stackthe maximum depth of the operand stack
localsthe number of local variables in the local variable array allocated upon invocation of this method, including the local variables used to pass parameters to the method on its invocation
args_sizethe number of method parameters, for non static method, the default number is 1, because each instance method will have a hidden parameter this
attribute_infothe jvm instructions of the current method, line 84 and line 85 means the local variable at 0 and 1 are pushed onto the operand stack, line 86 means the two int values are poped from stack, then an integer add of the two values is processed and the result is pushed back to stack, line 87 means the result value is popped from the operand stack of the current frame and pushed onto the operand stack of the frame of the invoker, any other values on the operand stack of the current method are discarded
LineNumberTabledescribe the correspondence between source line numbers and bytecode line numbers

Similarly, other methods in the class can be analyzed the same way.

Attributes

Last section is the attributes table of the class.

Here line 91 is SourceFile attribute whose value is the name of the code source file of the current class.

91 SourceFile: "ByteCodeTest.java"