DEV Community

Cover image for πŸ” Kotlin: Let's look at inline classes bytecode πŸ”
jbebar
jbebar

Posted on

πŸ” Kotlin: Let's look at inline classes bytecode πŸ”

Inline classes aim to reduce the overhead due to boxing of primitives. Inline classes are experimental since kotlin 1.3.
You can use them without warning with the following configuration added to the kotlin-maven-plugin, for instance in maven:

    <plugin>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-maven-plugin</artifactId>
        <version>1.4.21</version>
    ...
        <configuration>
            <args>
                <arg>-Xinline-classes</arg>
            </args>
        </configuration>
    ...
    </plugin>
Enter fullscreen mode Exit fullscreen mode

The inline keyword means the compiler will replace wherever possible the wrapper class by its enclosing primitive.
Let's have a look at this piece of code:

fun main() {
    val age = Age(18)
    val weight = Weight(87)
    println("Hello I am ${age.value}, my weight is ${weight.value} !")
}
class Age(val value: Int)
inline class Weight(val value: Int)
Enter fullscreen mode Exit fullscreen mode

Using intellij idea we can see the byte code generated by the main method by putting our cursor in the main function and searching the action ByteCode viewer, we obtain:

LINENUMBER 2 L0
    NEW Age
    DUP
    BIPUSH 18
    INVOKESPECIAL Age.<init> (I)V
    ASTORE 0
   L1
    LINENUMBER 3 L1
    BIPUSH 87
    INVOKESTATIC Weight.constructor-impl (I)I
    ISTORE 1
   L2
    LINENUMBER 4 L2
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "Hello I am "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    INVOKEVIRTUAL Age.getValue ()I
    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    LDC ", my weight is "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ILOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
    LDC " !"
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ASTORE 2
    ICONST_0
    ISTORE 3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 2
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V
   L3
    LINENUMBER 5 L3
    RETURN
   L4
    LOCALVARIABLE weight I L2 L4 1
    LOCALVARIABLE age LAge; L1 L4 0
    MAXSTACK = 3
    MAXLOCALS = 4
  // access flags 0x1009
  public static synthetic main([Ljava/lang/String;)V
    INVOKESTATIC MainKt.main ()V
    RETURN
    MAXSTACK = 0
    MAXLOCALS = 1
}
Enter fullscreen mode Exit fullscreen mode

Not very sexy 😱 right? Even without being a byte code expert let's decrypt the structure first:

We can recognize the different parts of our main function. From LINENUMBER 2 to LINENUMBER 4 we can see the declaration of age and weight variable.
From LINENUMBER 4 to LINENUMBER 5 we can see the invocation of the println function and the use of the java/lang/StringBuilder which is how a template string from Kotlin in translated into bytecode.

If we look again at the declaration of variables:

LINENUMBER 2 L0
    NEW Age
    DUP
    BIPUSH 18
    INVOKESPECIAL Age.<init> (I)V
    ASTORE 0
   L1
    LINENUMBER 3 L1
    BIPUSH 87
    INVOKESTATIC Weight.constructor-impl (I)I
    ISTORE 1
   L2
Enter fullscreen mode Exit fullscreen mode

We notice the two variables declaration are similar:

  • first: a value is pushed on the stack, with the BIPUSH 18 in the case of the age and 87 in the case of the weight command,
  • second: the constructor is invoked with INVOKESTATIC for the inline class weight and INVOKESPECIAL for the class Age.
  • third: a step of storage into the variable pool of the thread is done with ISTORE 0 for weight variable and ASTORE 1 for Age variable.

Let's focus on what makes the difference now:

We use ASTORE in the case of the Age variable and ISTORE in the case of the weight variable. If we go on this reference
of bytecode instructions for java, we can see that commands prefixed by A deals with references to object whereas I deals with integer.
So ASTORE 0 command designates the storage of an object reference at index 0 of the local variables of the thread, that's how the reference to the Age object is stored.
On the other hand, our Weight object doesn't exist in the byte code, it is directly stored at index 1 with the type integer, that is why we have ISTORE 1 instruction.

The StringBuilder calls confirms this replacement of the reference with a primitive:

LDC ", my weight is "
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ILOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
LDC " !"

The call to the integer primitive value stored at index 1 is made with ILOAD 1, so no object reference is used there either.

Another interesting thing to do is to replace the weight class with an integer, and see that the generated bytecode is very close from the one shown above. The main difference we will see are the static calls to the constructor of Weight INVOKESTATIC Weight.constructor-impl (I)I will not be in the bytecode anymore.

Conclusion

With the inline keyword, the kotlin compiler replaces in the bytecode the object reference with the primitive type being boxed.
The point of this is to reduce memory consumption while keeping the advantage of value classes for readability and domain design.
In some cases, though, the compiler doesn't replace the inline class with the primitive type, for instance if more than one property is boxed.
Why looking through the bytecode? Just out of curiosity :), I could also have used the decompiler plugin from intellij to decompile the bytecode into java code to see that the inlined class is indeed replaced, I let you try ;)

Sources:

For more details about inline classes: the kotlin documentation https://kotlinlang.org/docs/reference/inline-classes.html
To deep dive more into how java byte code is written, this article is useful : http://arhipov.blogspot.com/2011/01/java-bytecode-fundamentals.html
Wikipedia reference of bytecode instructions : https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

Top comments (0)