Why You Should Care About JVM

Mateusz Kubuszok

About me

  • Scala developer for 5 years

  • blogger - kubuszok.com

  • ebook author - Things you need to know about JVM (that matter in Scala)

  • OSS - scalaland.io

Plan

  • What is JVM

  • JVM types vs Scala types

  • Threads

  • Optimizations

What is JVM

Let’s see by example

object Test {

  def main(args: Array[String]): Unit = {
    val a = 1
    val b = 2
    val c = a + b
    println(c)
  }
}

Let’s see by example

class Test {

  public static void main(String[] args) {
    int a = 1;
    int b = 2;
    int c = a + b;
    scala.Predef$.MODULE$.println(
      scala.runtime.BoxRunTime.boxToInteger(c)
    );
  }
}

Let’s see by example

 0: iconst_1
 1: istore_2
 2: iconst_2
 3: istore_3
 4: iload_2
 5: iload_3
 6: iadd
 7: istore 4
 9: getstatic #22; scala.Predef$.MODULE$ : scala.Predef$
12: iload 4
14: invokestatic #28 ; scala.runtime.BoxesRunTime
                     ; .boxToInteger(int): java.lang.Integer
17: invokevirtual #32
; scala.Predef$.println(java.lang.Object): void

How bytecode works

Scala and JVM   page 5

How bytecode works

Scala and JVM   page 6

How bytecode works

Scala and JVM   page 7

How bytecode works

Scala and JVM   page 8

How bytecode works

Scala and JVM   page 9

How bytecode works

Scala and JVM   page 10

How bytecode works

Scala and JVM   page 11

How bytecode works

Scala and JVM   page 12

How bytecode works

Scala and JVM   page 13

How bytecode works

Scala and JVM   page 14

Reminder

class Test {

  public static void main(String[] args) {
    int a = 1;
    int b = 2;
    int c = a + b;
    // we finished here
    scala.Predef$.MODULE$.println(
      scala.runtime.BoxRunTime.boxToInteger(c)
    );
  }
}

How bytecode works

Scala and JVM   page 4

How bytecode works

Scala and JVM   page 15

How bytecode works

Scala and JVM   page 16

How bytecode works

Scala and JVM   page 17

How bytecode works

Scala and JVM   page 18

How bytecode works

Scala and JVM   page 19

Calls and returns

Scala and JVM   page 20

Stack traces

Scala and JVM   page 21

JVM types vs Scala types

Scala type hierarchy

Scala and JVM   page 3

Java type hierarchy

Scala and JVM   page 4

Comparison

  • scala.AnyVal s are automatically compiled to primitives OR boxed if needed, so they are dscribed using separate types

  • there is scala.Null subtype of all references

  • there is scala.Nothing subtype of all types

A bit more about numbers

  • JVM primitives are separate types with no common sub- or supertype

  • however they can be boxed with subclasses of java.lang.Number to share an interface

  • Scala’s numbers share AnyVal which has no common numeric methods, so instead we rely on scala.Numeric type class

Generics

// what we see

def head[A](nel: NonEmptyList[A]): A = ...

val value: String = head(stringNel)
// pseudocode of what JVM sees

// List can only contain java.lang.Object
def head(list: NonEmptyList): java.lang.Object = ...

// stringNel forgot its type parameter
val value: String = headOption(stringNel)
    .asInstanceOf[String] // so we have to retrieve it

Threads

In application

Scala and JVM   page 22

Creating a thread

// nothing interesting
val thread = new Thread()
thread.start()
// passing Runnable
val thread = new Thread(() => println("started"))
thread.start()
// extending Thread
val thread = new Thread {
  override def run(): Unit = println("started")
}
thread.start()

Thread pools

// 10 threads that could run submited tasks in parallel
val executorService = Executors.newFixedThreadPool(10)
executorService.submit(() => println("started"))
implicit val ec: ExecutionContext =
    ExecutionContext.fromExecutor(executorService)
// use in e.g. Future

Optimizations

Compilers

Scala and JVM   page 23

Examples

  • inlining

  • loop unrooling

  • null check elimination

  • dead code elimination

  • escape analysis

  • devirtualization

  • constant folding

  • …​

Some takeaways

  • JVM prefers short method (inlining and compilation)

  • JVM prefers not-too-deep call stack (ditto)

  • JVM perfers simple code (easier to optimize)

  • JVM prefers if we limit amount of implementations in the runtime (devirtualization → inlining)

  • a lot of optimizations we would do manually is aalredy done by JVM itself, so before doing anything - learn how to benchmark

What to avoid

jtrac callstack1

source: Peter Thomas

Summary

  • you cannot escape from JVM’s idiosyncracies forever, so it’s better to understand it

  • even with FP libraires sometimes knowledge about low-level stuff is important

  • if you want to be able to optimize you program, you HAVE to learn some basics

Sources for curious

Questions?

Thank you !