The document discusses exploring interesting Java features and how they are compiled and executed by the Java Virtual Machine (JVM). It begins with an introduction and overview of the topics that will be covered, including looking at Java bytecode, compiler logs, and generated native code. Examples of simple "Hello World" and math programs are provided and their compilation steps are examined at the bytecode, logging and native code levels to demonstrate how Java code is handled by the JVM.
Report
Share
Report
Share
1 of 126
Download to read offline
More Related Content
Down the Rabbit Hole
1. Down the Rabbit Hole
An adventure in JVM wonderland
Monday, September 30, 13
2. Me
• Charles Oliver Nutter
• Red Hat (yes, I have one; no, I don’t wear it)
• JRuby and JVM languages
• JVM hacking and spelunking
• @headius
Monday, September 30, 13
3. What are we going to
do today?
• Look at some interesting Java features
• See how they’re compiled to bytecode
• Watch what the JVM does with them
• Examine the actual native code they become
Monday, September 30, 13
5. Who AreYou?
• Java developers?
• Performance engineers?
• Debuggers?
• All of the above?
Monday, September 30, 13
6. Details Matter
• Cool features with hidden costs
• Inner classes
• Structural types in Scala
• Serialization
• How code design impacts performance
• What JVM can and can’t do for you
Monday, September 30, 13
7. Sufficiently Smart
Compiler
“HighLevelLanguage H may be slower than the
LowLevelLanguage L, but given a
SufficientlySmartCompiler this would not be the case”
http://c2.com/cgi/wiki?SufficientlySmartCompiler
Monday, September 30, 13
8. Sufficiently Smart
Compiler
If you wait long enough*, the JVM will eventually
optimize everything perfectly and even terrible
code will perform well.
* for some definition of “long”
Monday, September 30, 13
10. Vocabulary
• Source
• The .java text that represents a program
• Bytecode
• The binary version of the program that
all JVMs can load and execute
Monday, September 30, 13
11. Vocabulary
• Native code
• Machine code specific to the current
platform (OS, CPU) that represents the
program in a form the CPU can execute
directly
• Heap
• The JVM-controlled area of memory
where Java objects live
Monday, September 30, 13
12. Vocabulary
• JIT
• “Just In Time” (compilation) that turns one
program form into a lower program form,
e.g. bytecode into native code at
runtime
• AOT
• Compilation that occurs before
runtime
Monday, September 30, 13
13. JVM 101
Java source JVM bytecode
javac
JVM bytecode
Bytecode
interpreter
runs inside gather
information
JIT
compiler
triggers
Native code
produces
executes
backs off
Monday, September 30, 13
14. Vocabulary
• Inlining
• Inserting the code of a called method
into the caller, avoiding overhead of the
call and optimizing the two together
• Optimization
• Doing the least amount of work needed
to accomplish some goal
Monday, September 30, 13
15. Inlining Instance
Method
Load target and
arguments
Target type is same?
Method
lookup
Run target
code directly
Yes
No
Run target
method as a call
Monday, September 30, 13
16. Inlining Static or Special
Method
Load arguments
Run target
code directly
Monday, September 30, 13
17. Our Tools
• javac, obviously
• javap to dump .class data
• -XX:+PrintCompilation
• -XX:+PrintInlining
• -XX:+PrintAssembly
Monday, September 30, 13
20. Level 1: Bytecode
• javap
• Java class file disassembler
• Dump structure, data, metadata, and code
Monday, September 30, 13
21. $ javap -cp dist/RabbitHole.jar
com.headius.talks.rabbithole.HelloWorld
Compiled from "HelloWorld.java"
public class com.headius.talks.rabbithole.HelloWorld {
public com.headius.talks.rabbithole.HelloWorld();
public static void main(java.lang.String[]);
}
Monday, September 30, 13
22. $ javap -cp dist/RabbitHole.jar
-c
com.headius.talks.rabbithole.HelloWorld
Compiled from "HelloWorld.java"
public class com.headius.talks.rabbithole.HelloWorld {
...
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/
System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello, world!
5: invokevirtual #4 // Method java/io/
PrintStream.println:(Ljava/lang/String;)V
8: return
}
Monday, September 30, 13
23. Our First Bytecodes
• getstatic/putstatic - static field access
• ldc - load constant value on stack
• invokevirtual - call a concrete instance
method
• return - return from a void method
Monday, September 30, 13
24. $ javap -cp dist/RabbitHole.jar
-c
com.headius.talks.rabbithole.HelloWorld
Compiled from "HelloWorld.java"
public class com.headius.talks.rabbithole.HelloWorld {
...
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/
System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello, world!
5: invokevirtual #4 // Method java/io/
PrintStream.println:(Ljava/lang/String;)V
8: return
}
Monday, September 30, 13
25. Level 2: Compiler Logs
• -XX:+PrintCompilation
• Display methods as they compile
• -XX:+PrintInlining
• Display inlined methods as nested
Monday, September 30, 13
26. JVM JIT
• Code is interpreted first
• After some threshold, JIT fires
• Classic JVM went straight to “client” or
“server”
• Tiered compiler goes to “client plus
profiling” and later “server”
Monday, September 30, 13
27. public class HelloWorld {
public static void main(String[] args) {
for (int i = 0; i < 100000; i++) {
hello();
}
}
private static void hello() {
System.err.println("Hello, world!");
}
}
Monday, September 30, 13
37. Too big!
• Server produces ~2700 bytes of ASM
• Client produces ~594 bytes of ASM
• Most of server output is from inlining
• More profiling, more code, more perf
• ...and slower startup
Monday, September 30, 13
38. public class Tiny1 {
public static void main(String[] args) {
for (int i = 0; i < 100000; i++) {
tiny();
}
}
public static int tiny() {
return 1 + 1;
}
}
Monday, September 30, 13
39. public static int tiny();
Code:
0: iconst_2
1: ireturn
iconst_2: load integer 2 on stack
ireturn: return int
Monday, September 30, 13
40. 110 3 b com.headius.talks.rabbithole.Tiny1::tiny (2 bytes)
111 4 % b com.headius.talks.rabbithole.Tiny1::main @ 2 (19 bytes)
@ 8 com.headius.talks.rabbithole.Tiny1::tiny
(2 bytes) inline (hot)
Monday, September 30, 13
41. {0x000000010994c3c0} 'tiny' '()I' in 'com/headius/talks/rabbithole/Tiny1'
# [sp+0x40] (sp of caller)
0x0000000109e566a0: mov %eax,-0x14000(%rsp)
0x0000000109e566a7: push %rbp
0x0000000109e566a8: sub $0x30,%rsp ;*iconst_2
; -
com.headius.talks.rabbithole.Tiny1::tiny@0 (line 11)
0x0000000109e566ac: mov $0x2,%eax
0x0000000109e566b1: add $0x30,%rsp
0x0000000109e566b5: pop %rbp
0x0000000109e566b6: test %eax,-0x9a05bc(%rip) # 0x00000001094b6100
; {poll_return}
0x0000000109e566bc: retq
Monday, September 30, 13
42. {0x000000010994c3c0} 'tiny' '()I' in 'com/headius/talks/rabbithole/Tiny1'
# [sp+0x40] (sp of caller)
0x0000000109e566a0: mov %eax,-0x14000(%rsp)
0x0000000109e566a7: push %rbp
0x0000000109e566a8: sub $0x30,%rsp ;*iconst_2
; -
com.headius.talks.rabbithole.Tiny1::tiny@0 (line 11)
0x0000000109e566ac: mov $0x2,%eax
0x0000000109e566b1: add $0x30,%rsp
0x0000000109e566b5: pop %rbp
0x0000000109e566b6: test %eax,-0x9a05bc(%rip) # 0x00000001094b6100
; {poll_return}
0x0000000109e566bc: retq
Monday, September 30, 13
55. Java Features
• final fields
• synchronized
• string switch
• lambda
Monday, September 30, 13
56. #1: Final Fields
• Final fields can’t be modified
• The pipeline can take advantage
• ...but it doesn’t always
Monday, September 30, 13
57. public class Fields {
private static final String MY_STRING =
"This is a static string";
private static final String MY_PROPERTY =
System.getProperty("java.home");
public static void main(String[] args) {
System.out.println(MY_STRING);
System.out.println(MY_PROPERTY);
}
}
Monday, September 30, 13
58. public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #9 // String This is a static string
5: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
11: getstatic #11 // Field MY_PROPERTY:Ljava/lang/String;
14: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
private static final String MY_STRING =
"This is a static string";
private static final String MY_PROPERTY =
System.getProperty("java.home");
Monday, September 30, 13
59. private static int addHashes() {
return MY_STRING.hashCode() + MY_PROPERTY.hashCode();
}
Monday, September 30, 13
61. private final String myString = "This is an instance string";
private final String myProperty = System.getProperty("java.home");
public int addHashes2() {
return myString.hashCode() + myProperty.hashCode();
}
Monday, September 30, 13
62. private int addHashes2();
Code:
0: ldc #2 // String This is an instance string
2: invokevirtual #18 // Method java/lang/String.hashCode:()I
5: aload_0
6: getfield #6 // Field myProperty:Ljava/lang/String;
9: invokevirtual #18 // Method java/lang/String.hashCode:()I
12: iadd
13: ireturn
Monday, September 30, 13
63. movabs $0x7aab6d318,%rcx ; {oop("This is an instance string")}
mov 0x10(%rcx),%r10d ;*getfield hash
; - String::hashCode@1 (line 1458)
; - Fields::addHashes2@2 (line 40)
Monday, September 30, 13
66. #2: Concurrency Stuff
• What does “synchronized” do?
• What does “volatile” do?
Monday, September 30, 13
67. public class Concurrency {
public static void main(String[] args) {
System.out.println(getTime());
System.out.println(getTimeSynchronized());
}
public static long getTime() {
return System.currentTimeMillis();
}
public static synchronized long getTimeSynchronized() {
return System.currentTimeMillis();
}
}
Monday, September 30, 13
75. Vocabulary
• Lock coarsening
• Expanding the use of multiple fine-
grained locks into a single coarse-grained
lock
• Lock eliding
• Eliminating locking when it will not affect
the behavior of the program
Monday, September 30, 13
77. 0x000000010ef0665f: movabs $0x7aab6bee8,%r11
; {oop(a 'java/lang/Class' = '.../Concurrency')}
0x000000010ef06669: lea 0x10(%rsp),%rbx
0x000000010ef0666e: mov (%r11),%rax
0x000000010ef06671: test $0x2,%eax
0x000000010ef06676: jne 0x000000010ef0669f
0x000000010ef0667c: or $0x1,%eax
0x000000010ef0667f: mov %rax,(%rbx)
0x000000010ef06682: lock cmpxchg %rbx,(%r11)
0x000000010ef06687: je 0x000000010ef066bc
Monday, September 30, 13
78. Volatile
• Forces commit of memory
• Forces code ordering
• Prevents some optimizations
• Similar impact to unecessary locking
• ...but it can’t ever be removed
Monday, September 30, 13
80. public class RubyBasicObject ... {
private static final boolean DEBUG = false;
private static final Object[] NULL_OBJECT_ARRAY = new Object[0];
// The class of this object
protected transient RubyClass metaClass;
// zeroed by jvm
protected int flags;
// variable table, lazily allocated as needed (if needed)
private volatile Object[] varTable = NULL_OBJECT_ARRAY;
LOCK
Maybe it’s not such a good idea to pre-init a volatile?
Wednesday, July 27, 2011
Monday, September 30, 13
81. #3: String Switch
• Added in Java 7
• ...and there was much rejoicing
• But how does it really work?
Monday, September 30, 13
82. A Normal Switch
• Variable switch parameter
• Constant case values
• Branch based on a table (fast) for narrow
range of cases
• Branch based on a lookup (less fast) for
broad range of cases
Monday, September 30, 13
83. public class StringSwitch {
public static void main(String[] args) {
String count = "unknown";
switch (args.length) {
case 0: count = "zero"; break;
case 1: count = "one"; break;
case 2: count = "two"; break;
}
...
Monday, September 30, 13
87. Comparison
• tableswitch is O(1)
• Indexed lookup of target
• lookupswitch is O(log n)
• Binary search for target
Monday, September 30, 13
88. Get to the point already!
Monday, September 30, 13
89. The Point
• What kind of switch do we use for String?
• Table doesn’t work for hashcodes
• Lookup might collide
• Answer: both, plus .equals()
Monday, September 30, 13
90. static String chooseGreeting(String language) {
switch (language) {
case "Java": return "I love to hate you!";
case "Scala": return "I love you, I think!";
case "Clojure": return "(love I you)";
case "Groovy": return "I love ?: you";
case "Ruby": return "I.love? you # => true";
default: return "Who are you?";
}
}
Monday, September 30, 13
92. 74: aload_1
75: ldc #14 // String Scala
77: invokevirtual #17 // Method String.equals:(Ljava/lang/Object;)Z
80: ifeq 127
83: iconst_1
84: istore_2
Same hidden int variable now = 1
Monday, September 30, 13
93. 127: iload_2
128: tableswitch { // 0 to 4
0: 164
1: 167
2: 170
3: 173
4: 176
default: 179
}
164: ldc #20 // String I love to hate you!
166: areturn
167: ldc #21 // String I love you, I think!
169: areturn
170: ldc #22 // String (love I you)
172: areturn
173: ldc #23 // String I love ?: you
175: areturn
176: ldc #24 // String I.love? you # => true
178: areturn
179: ldc #25 // String Who are you?
181: areturn
A-ha! There it is!
Monday, September 30, 13
94. static String chooseGreeting2(String language) {
int hash = language.hashCode();
int target = -1;
switch (hash) {
case 2301506: if (language.equals("Java")) target = 0; break;
case 79698214: if (language.equals("Scala"))target = 1; break;
case -1764029756: if (language.equals("Clojure"))target = 2; break;
case 2141368366: if (language.equals("Groovy"))target = 3; break;
case 2558458: if (language.equals("Ruby"))target = 3; break;
}
switch (target) {
case 0: return "I love to hate you!";
case 1: return "I love you, I think!";
case 2: return "(love I you)";
case 3: return "I love ?: you";
case 4: return "I.love? you # => true";
default: return "Who are you?";
}
}
Monday, September 30, 13
95. It’s just a hash table!
Monday, September 30, 13
96. #4: Lambda Expressions
• New for Java 8
• ...and there was much rejoicing
• Key goals
• Lighter-weight than inner classes
• No class-per-lambda
• Optimizable by JVM
Monday, September 30, 13
97. public class LambdaStuff {
public static void main(String[] args) {
List<String> list = Arrays.asList(
"Clojure",
"Java",
"Ruby",
"Groovy",
"Scala"
);
for (int i = 0; i < 100000; i++) {
doSort(list);
getRest(list);
getAllCaps(list);
getInitials(list);
getInitialsManually(list);
}
Monday, September 30, 13
98. public static void doSort(List<String> input) {
Collections.sort(input,
(a,b)->Integer.compare(a.length(), b.length()));
}
Monday, September 30, 13
100. public static void doSort(java.util.List<java.lang.String>);
Code:
0: aload_0
1: invokedynamic #36, 0
// InvokeDynamic #4:compare:()Ljava/util/Comparator;
6: invokestatic #37
// Method java/util/Collections.sort ...
9: return
InvokeDynamic is used to create the initial lambda
object and then cache it forever.
Compare to anonymous inner classes, where an
instance is created every time.
Monday, September 30, 13
102. BootstrapMethods:
...
4: #142 invokestatic java/lang/invoke/LambdaMetafactory.metafactory...
...bunch of types here
Method arguments:
#167 (Ljava/lang/Object;Ljava/lang/Object;)I
#168 invokestatic LambdaStuff.lambda$2:(Ljava/lang/String;Ljava/lang/String;)I
#169 (Ljava/lang/String;Ljava/lang/String;)I
LambdaMetaFactory generates an implementation of
our interface (Comparator here) using
Method Handles (from JSR292)
Monday, September 30, 13
103. BootstrapMethods:
...
4: #142 invokestatic java/lang/invoke/LambdaMetafactory.metafactory...
...bunch of types here
Method arguments:
#167 (Ljava/lang/Object;Ljava/lang/Object;)I
#168 invokestatic LambdaStuff.lambda$2:(Ljava/lang/String;Ljava/lang/String;)I
#169 (Ljava/lang/String;Ljava/lang/String;)I
LambdaMetaFactory generates an implementation of
our interface (Comparator here) using
Method Handles (from JSR292)
Monday, September 30, 13
104. private static int lambda$2(java.lang.String, java.lang.String);
Code:
0: aload_0
1: invokevirtual #53 // Method java/lang/String.length:()I
4: aload_1
5: invokevirtual #53 // Method java/lang/String.length:()I
8: invokestatic #54 // Method java/lang/Integer.compare:(II)I
11: ireturn
Lambda body is just a static method;
all state is passed to it.
Because the wrapper is generated and the body
is just a static method, we have no extra classes
and potentially no allocation.
Monday, September 30, 13
106. The Problem
• In order to inline code, we need:
• A consistent target method
• A unique path through the code
• Collections.sort’s lambda callback
• Will see many different methods
• Will be called via many different paths
Monday, September 30, 13
107. Caller 1
Caller 2
Caller 3
Caller 4
sort
Lambda 1
Lambda 2
Lambda 3
Lambda 4
Too many paths!
JVM can’t cope!
Monday, September 30, 13
108. public static String getInitials(List<String> input) {
return input.stream()
.map(x->x.substring(0,1))
.collect(Collectors.joining());
}
public static String getInitialsManually(List<String> input) {
StringBuilder builder = new StringBuilder();
UnaryOperator<String> initial = (String x)->x.substring(0,1);
for (String s : input) {
builder.append(initial.apply(s));
}
return builder.toString();
}
Monday, September 30, 13
109. public static void time(Object name, int iterations, Runnable body) {
long start = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
body.run();
}
System.out.println(name.toString()
+ ": "
+ (System.currentTimeMillis() - start));
}
Monday, September 30, 13
110. Function<List<String>, String> getInitials =
LambdaStuff::getInitials;
Function<List<String>, String> getInitialsManually =
LambdaStuff::getInitialsManually;
for (int i = 0; i < 10; i++) {
time("getInitials", 1000000,
()->getInitials.apply(list));
time("getInitialsManually", 1000000,
()->getInitialsManually.apply(list));
}
Monday, September 30, 13
124. Vocabulary
• Escape analysis
• Determining that the data enclosed
inside an object is the only part of the
object needed, so the object does not
need to be allocated
Monday, September 30, 13
125. Escape Analysis
Object created
Object goes
out of scope
Object used only
in this method
Field data put into
locals
No object to
dereference
Object data used
from fields
Monday, September 30, 13
126. Breaking EA
• Object assigned to a non-EA object’s field
• Object may go to a method not inlined
• Object may go to a branch not yet followed
• or potentially, crosses any branch at all
Monday, September 30, 13