Understanding JVM Bytecode Instructions: Part 1 - Loading, Arithmetic, and Type Conversion

This article focuses on bytecode instructions, providing an in-depth yet accessible explanation of various bytecode instruction types, including: loading and storing, arithmetic operations, type conversion, object creation and access, method invocation and return, control flow, exception handling, and synchronization.

Due to the extensive variety of bytecode instructions, this article serves as the first part, covering loading and storing, arithmetic, and type conversion instructions.

Use the jclasslib plugin in IDEA to view compiled bytecode instructions.

Bytecode Instruction Set

Most instructions begin with prefixes like i (int), l (long), f (float), d (double), a (reference).

Note: byte, char, short, and boolean types are converted to int in HotSpot and executed using int-type bytecode instructions.

Bytecode instructions are generally categorized into:

  1. Loading and Storing Instructions
  2. Arithmetic Instructions
  3. Type Conversion Instructions
  4. Object Creation and Access Instructions
  5. Method Invocation and Return Instructions
  6. Operand Stack Management Instructions
  7. Control Flow Instructions
  8. Exception Handling Instructions
  9. Synchronization Control Instructions

In HotSpot, each method corresponds to a set of bytecode instructions.

These bytecode instructions operate on the local variable table and operand stack within the method's corresponding stack frame.

Bytecode instructions consist of bytecode operation codes and operands (operands may come from the local variable table, constant pool, or be constants).

Loading and Storing Instructions

Loading

Loading instructions push operands onto the operand stack (from the local variable table or constant pool).

  • Local Variable Table Loading Instructions

    • i/l/f/d/aload followed by the operand indicates which slot in the local variable table to retrieve the value from
    • iload_0: Retrieve the int value from slot 0 of the local variable table
  • Constant Loading Instructions

    • Can be categorized into three types based on the range of constants loaded: const < push < ldc

Storing

Storing instructions pop the top element from the operand stack and store it in a specific slot of the local variable table.

  • Storing Instructions

    • i/l/f/d/astore followed by the operand indicates which slot in the local variable table to store the value in
    • istore_1: Pop the top int element from the stack and store it in slot 1 of the local variable table

Note: At compile time, the number of slots required in the local variable table and the maximum depth of the operand stack are determined (to save space, local variable slots may be reused).

Load 100 from the constant pool and store it in slot 1 of the local variable table, load 200 from the constant pool and store it in slot 2 of the local variable table (where slot 0 of the local variable table stores this).

Arithmetic Instructions

Arithmetic instructions pop two top elements from the operand stack, perform an operation, and push the result back onto the stack.

They use postfix notation (Reverse Polish notation), for example: 3 4 + => 3 + 4.

Notes

  1. When the divisor is 0, an ArithmeticException is thrown
  2. Floating-point to integer conversion truncates toward zero
  3. Floating-point calculations may lose precision
  4. Infinity results in infinite values
  5. NaN results in undefined calculation values
public void testArithmetic() {
    double d1 = 10 / 0.0;
    // Infinity
    System.out.println(d1);
    
    double d2 = 0.0 / 0.0;
    // NaN
    System.out.println(d2);
    
    // Truncation toward zero: floating-point to integer conversion
    // 5
    System.out.println((int) 5.9);
    // -5
    System.out.println((int) -5.9);
    
    // Rounding to nearest: floating-point arithmetic
    // 0.060000000000000005
    System.out.println(0.05 + 0.01);
    
    // Throws ArithmeticException: / by zero
    System.out.println(1 / 0);
}

Type Conversion Instructions

Type conversion instructions can be divided into widening type conversions and narrowing type conversions (corresponding to implicit and explicit conversions of primitive types).

Widening Type Conversion

Conversion from smaller to larger ranges

  • int -> long -> float -> double

    • i2l, i2f, i2d
    • l2f, l2d
    • f2d

byte, short, char use int-type instructions.

Note: When converting long to float or double, precision loss may occur.

public void testWideningConversion() {
    long l1 = 123412345L;
    long l2 = 1234567891234567899L;
    
    float f1 = l1;
    // Result: 1.23412344E8 => 123412344
    // l1 = 123412345L
    System.out.println(f1);
    
    double d1 = l2;
    // Result: 1.23456789123456794E18 => 1234567891234567940
    // l2 = 1234567891234567899L
    System.out.println(d1);
}

Narrowing Type Conversion

Conversion from larger to smaller ranges

  • int -> byte, char, short: i2b, i2c, i2s
  • long -> int: l2i
  • float -> long, int: f2l, f2i
  • double -> float, long, int: d2f, d2l, d2i

If long, float, or double need to be converted to byte, char, or short, they can first be converted to int and then to the corresponding type.

Narrowing type conversion may result in precision loss.

Special cases for NaN and Infinity:

public void testNarrowingConversion() {
    double d1 = Double.NaN;
    double d2 = Double.POSITIVE_INFINITY;
    
    int i1 = (int) d1;
    int i2 = (int) d2;
    // 0
    System.out.println(i1);
    // true
    System.out.println(i2 == Integer.MAX_VALUE);
    
    long l1 = (long) d1;
    long l2 = (long) d2;
    // 0
    System.out.println(l1);
    // true
    System.out.println(l2 == Long.MAX_VALUE);
    
    float f1 = (float) d1;
    float f2 = (float) d2;
    // NaN
    System.out.println(f1);
    // Infinity
    System.out.println(f2);
}

Converting NaN to an integer results in 0.

Converting positive or negative infinity to an integer results in the maximum or minimum value of that type.

Object Creation and Access Instructions

Object creation and access instructions include: creation instructions, field access instructions, array manipulation instructions, and type checking instructions.

Creation Instructions

new: Create an instance

newarray: Create a one-dimensional array of primitive types

anewarray: Create a one-dimensional array of reference types

multianewarray: Create multi-dimensional arrays

Note: Creation here can be understood as memory allocation. When only one dimension of a multi-dimensional array is allocated, anewarray is used.

Field Access Instructions

getstatic: Read operation on static fields

putstatic: Write operation on static fields

getfield: Read operation on instance fields

putfield: Write operation on instance fields

Read operations: Push the field to be read onto the stack.

Write operations: Pop the value to be written from the stack and write it to the corresponding field.

Array Manipulation Instructions
  • b/c/s/i/l/f/d/a aload: Indicates pushing an element from a specific index in the array onto the stack (read)

    • Required parameters from top to bottom of the stack: index position, array reference
  • b/c/s/i/l/f/d/a astore: Indicates popping a value and writing it to a specific index in the array (write)

    • Required parameters from top to bottom of the stack: value to be written, index position, array reference

Note: Instructions starting with b are applicable to both byte and boolean.

  • arraylength: First pops the array reference, then pushes the obtained array length onto the stack
Type Checking Instructions

instanceof: Determine if an object is an instance of a specific class

checkcast: Check if a reference type can be cast

Summary

Due to the large number and length of bytecode instructions, they will be divided into two parts for in-depth explanation. This article, as the first part, provides an in-depth explanation of bytecode instruction introduction, loading and storing instructions, arithmetic instructions, type conversion instructions, and object creation and access instructions.

Most bytecode instructions start with i, l, f, d, a, corresponding to int, long, float, double, and reference respectively, where byte, char, short, and boolean are converted to int for execution.

Bytecode instructions consist of bytecode operation codes and required operands, which may come from the local variable table or constant pool.

Loading instructions load data from the local variable table or constant pool, while storing instructions store data into corresponding slots of the local variable table. The 0th slot of the local variable table for instance methods is commonly used to store this, and slots may be reused if variables are local to the method.

Arithmetic instructions provide arithmetic rules for various types and operations, using postfix notation on the operand stack.

Type conversion is divided into widening and narrowing, both of which may result in precision loss.

Object creation and access instructions include creating objects, accessing instance and static fields, manipulating arrays, and type checking instructions.

Tags: JVM Bytecode java Java Virtual Machine class file format

Posted on Thu, 14 May 2026 16:32:10 +0000 by bamse