object Test {
def main(args: Array[String]): Unit = {
val a = 1
val b = 2
val c = a + b
println(c)
}
}
Mateusz Kubuszok
Scala developer for 5 years
blogger - kubuszok.com
ebook author - Things you need to know about JVM (that matter in Scala)
OSS - scalaland.io
What is JVM
JVM types vs Scala types
Threads
Optimizations
object Test {
def main(args: Array[String]): Unit = {
val a = 1
val b = 2
val c = a + b
println(c)
}
}
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)
);
}
}
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
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)
);
}
}
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
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
// 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
// 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()
// 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
inlining
loop unrooling
null check elimination
dead code elimination
escape analysis
devirtualization
constant folding
…
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
source: Peter Thomas
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
Oracle’s JVM specifications (and Oracle’s documentation in general)
Daniel Spiewak’s gists about Thread Pools and Optimizing JVM
Aleksey ShipilĂ«v’s blog
Fabio Labella’s presentation How do Fibers Work? A Peek Under the Hood
my own book about JVM and Scala