Kotlin on the JVM – Bytecode Generation

Kotlin on the JVM - How can it provide so many features?

Introduction

What exactly is a "JVM language", what is Kotlin? Isn’t only Java meant to run on the JVM?
Kotlin provides many features that aren’t available in Java such as proper function types, extension functions and data classes. How is this even possible? I’ve taken a deeper look at how Kotlin works under the hood and what "JVM language" actually means. We'll be having a look at Kotlin's bytecode generation. If you also thought about these things already, this article should bring some light into the darkness 🙂

For a more detailed introduction to Kotlin’s features you can have a look at my Getting Started Guide.

The Java Virtual Machine

A quick and simple definition: The Java Virtual Machine is used by computers to run Java bytecode.
Actually there’s a lot more to learn about this complex tool, which is described extensively in Oracle’s Spec.

As you might already know, the JVM is an abstract virtual computer running on various operating systems. In fact, the JVM is what makes Java "platform independent", because it acts as an abstraction between the executed code and the OS.
Just like any real computer, the JVM provides a defined set of instructions which can be used by a program and are translated to machine specific instructions by the JVM itself later on.

As described in the JVM Spec, the Java Virtual Machine doesn’t know anything about the programming language Java. However, it defines the binary format class which is a file containing machine instructions (= bytecodes) to be executed (beside some more information). This is a very interesting point, because it actually means, that

  1. the JVM isn’t only dedicated to Java as a programming language.
  2. you are free to choose a technology for creating JVM programs as long as you provide proper class files that are compliant to the very strict constraints.
  3. regardless of programming languages, any Java bytecode can interoperate with other Java bytecode on the JVM.

Creation of Class Files

The process of creating class files from human-readable source code is what a compiler does. One example is Oracle’s Java Compiler shipped with the JDK (javac) that is capable of compiling .java files to .class files.
In Addition to Java, many other JVM languages have emerged in the last few years, all of which try to provide an alternative abstraction for the developers to create programs for the JVM.
One of these languages is Kotlin.

Kotlin Bytecode Generation - What is Kotlin?

As stated in the official FAQs, "Kotlin produces Java compatible bytecode", which means that the Kotlin compiler is capable of transforming all the nice features into JVM compatible instructions and this can even be observed using IntelliJ IDEA tools.
Let’s look at some examples:

Top Level Functions

Kotlin
//File.kt
fun foobar(){}

This simple top-level function defined in a .kt file can be investigated with IntelliJ:
"Tools → Kotlin → Show Kotlin Bytecode" will open a new window inside the IDE providing a live preview of the Java bytecode the compiler would create for the currently edited .kt file.

Java bytecode
public final class de/swirtz/kotlin/FileKt {
  // access flags 0x19
  public final static foobar()V
   L0
    LINENUMBER 3 L0
    RETURN
   L1
    MAXSTACK = 0
    MAXLOCALS = 0

  @Lkotlin/Metadata;
  // compiled from: File.kt
}

I’m afraid only a few people can actually read these files, which is why we can also choose the option "Decompile". Afterwards we’ll be presented a Java class enclosing the functionality previously described with Kotlin:

Top Level Function decompiled
public final class FileKt {
   public static final void foobar() {
   }
}

As you can see and probably already know, a Kotlin top level class is compiled into a final Java class with a static function [1]. Let’s see a more difficult one:

Classes and Extension Functions

Kotlin
class MyClass(val i: Int)

fun MyClass.myExtension(value: String) = value.length

This one shows a simple class MyClass with a property of type Int, as well as a top level extension function.
First, we should have a look at what the class is compiled to, which is quite interesting as we used a primary constructor and the val keyword here.

Class in Java
public final class MyClass {
   private final int i;

   public final int getI() {
      return this.i;
   }

   public MyClass(int i) {
      this.i = i;
   }
}

As we would expect: the property is a final member being assigned in the single constructor. Yet, so much simpler in Kotlin 🙂

Extension Function decompiled
public final class FileKt {
   public static final int myExtension(@NotNull MyClass $receiver, @NotNull String value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      Intrinsics.checkParameterIsNotNull(value, "value");
      return value.length();
   }
}

The extension function itself is compiled to a static method with its receiver object as a parameter in the Java code.
One thing we can also observe in the example is the use of a class called Intrinsics. This one is part of the Kotlin stdlib and is utilized because the parameters are required to be not null.
Let’s see what would happen if we changed the inital extension function’s parameter to value: String? and of course access length in a safe way.

Extension Function with nullable Parameter decompiled
public final class FileKt {
   @Nullable
   public static final Integer myExtension(@NotNull MyClass $receiver, @Nullable String value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return value != null?Integer.valueOf(value.length()):null;
   }
}

Checking value is not necessary anymore since we told the compiler that null is an acceptable thing to point to.
The next example is a bit more tricky. It’s the one with the greatest difference between Kotlin and Java code:

Ranges
Kotlin
fun loopWithRange(){
    for(i in 5 downTo 1 step 2){
        print(i)
    }
}
decompiled
 public static final void loopWithRange() {
      IntProgression var10000 = RangesKt.step(RangesKt.downTo(5, 1), 2);
      int i = var10000.getFirst(); //i: 5
      int var1 = var10000.getLast(); //var1: 1
      int var2 = var10000.getStep(); //var2: -2
      if(var2 > 0) {
         if(i > var1) {
            return;
         }
      } else if(i < var1) {
         return;
      }

      while(true) {
         System.out.print(i);
         if(i == var1) {
            return;
         }

         i += var2;
      }
   }

Although the Java code still is quite comprehensible, probably nobody would write it in real life, because a simple for could do it, too. We need to consider that downTo and step are infix notations, which are function calls actually. In order to provide this flexibility, a little more code just seems to be necessary.

What do you think? It doesn't look that nice, although the Kotlin code is brilliant, right?

Conclusion

I think, most of the time you don’t really care about what the Kotlin compiler produces behind the scenes. Yet, I find observing it really interesting and helpful as it supports answering my initial questions in some way. It provides an answer to "What is Kotlin" in a way 😉 Of course, Kotlin is much more than just abstracting Java’s operators since it also provides so many extensions to existing Java classes like List or String.
Nevertheless we also saw that sometimes the compiled Java code is more verbose than it had to be. Could this impact performance? Yes indeed, it does have minor effects. Have a look at this presentation by Dmitry Jemerov if you’re interested in more "Kotlin → Java bytecode" examples that also consider performance.

Finally, if you want to read about Kotlin's beautiful features I recommend the book Kotlin in Action to you!

Let me know what you think about it and get in touch if you like!


 1. This structure looks like what extension functions mean to replace: utility classes

3 thoughts on “Kotlin on the JVM – Bytecode Generation

Leave a Reply

Your email address will not be published. Required fields are marked *