Abstract: How would the plain Java code look for our try-with-resource constructs? Watch how 4 lines of code are expanded into 39 LOC and 170 bytecodes. We also look at how Java 9 has reduced this slightly.
Welcome to the 259th edition of The Java(tm) Specialists' Newsletter, written at 30000 feet en route to Duesseldorf. As I waddled towards the airport terminal, a bored official stopped me: "German or English?" With three German and one Dutch grandparents, they usually skip asking me. Perhaps my rough Cretan mountain man appearance needs some grooming? I replied with a perfect German accent: "Wie Sie wollen." - as you wish. "Ihren Ausweis bitte." And out came my Greek ID. With the name of Heinz. Gets them every time. Poor policeman was dumbstruck.
My new Java Patterns Course is available for purchase. To whet your appetite, we have bundled it in a limited edition Software Artist Pack. There is an easter egg, which should have expired on the 30th of June, but I will keep it active until Friday the 6th July 2018. If you cannot figure out the easter egg, please pop me an email - but before this Friday - and I will tell you. Or you can search for it :-) That's more fun!
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
A couple of months ago, a student of our
Refactoring
to Java 8 Streams Course posted a question about why
try-with-resource would not add a suppressed
NullPointerException if the resource declared in the try()
was null. The comment is here
and I've made the section publicly visible. Unfortunately
this does not show the comments. Whilst attempting to answer
the question, I realized that I did not really know what the
Java code would look like without the sugarcoating of
try-with-resource. I hacked away with javap -c until I was
happy that my manual and automatic versions were equivalent.
Let's start with the try-with-resource version:
try (InputStream io = getNullStream()) {
FileInputStream fi = new FileInputStream("somefile.bin");
io.available();
}
Method getNullStream() returns
null, and "somefile.bin" did not
exist, so I expected a
FileNotFoundException, with a suppressed
NullPointerException. I thought that the generated bytecode
would be equivalent to:
InputStream io = getNullStream();
Throwable throwable = null;
try {
FileInputStream fi = new FileInputStream("somefile.bin");
io.available();
} catch (Throwable t) {
throwable = t;
throw t;
} finally {
if (throwable == null) {
io.close();
} else {
try {
io.close();
} catch (Throwable th) { // NullPointerException
throwable.addSuppressed(th);
}
}
}
After moving code up and down, and adding a check for whether
io != null, I found the following plain Java
code equivalent to the Java 7 try-with-resource construct.
Notice there is a check whether io is non-null
before attempting to close it. Very sensible indeed.
InputStream io = getNullStream();
Throwable throwable = null;
try {
FileInputStream fi = new FileInputStream("somefile.bin");
io.available();
} catch (Throwable t) {
throwable = t;
throw t;
} finally {
if (io != null) {
if (throwable != null) {
try {
io.close();
} catch (Throwable t) {
throwable.addSuppressed(t);
}
} else {
io.close();
}
}
}
Here is the disassembled bytecode, using the tool javap -c.
The generated code is identical in Java 7, 8, 9 and 10:
0: invokestatic #4 // getNullStream:InputStream;
3: astore_0
4: aconst_null
5: astore_1
6: new #5 // class java/io/FileInputStream
9: dup
10: ldc #6 // String somefile.bin
12: invokespecial #7 // FileInputStream."<init>"(String)
15: astore_2
16: aload_0
17: invokevirtual #8 // InputStream.available()
20: pop
21: aload_0
22: ifnull 90
25: aload_1
26: ifnull 45
29: aload_0
30: invokevirtual #9 // InputStream.close()
33: goto 90
36: astore_2
37: aload_1
38: aload_2
39: invokevirtual #11 // Throwable.addSuppressed(Throwable)
42: goto 90
45: aload_0
46: invokevirtual #9 // InputStream.close()
49: goto 90
52: astore_2
53: aload_2
54: astore_1
55: aload_2
56: athrow
57: astore_3
58: aload_0
59: ifnull 88
62: aload_1
63: ifnull 84
66: aload_0
67: invokevirtual #9 // java/io/InputStream.close()
70: goto 88
73: astore 4
75: aload_1
76: aload 4
78: invokevirtual #11 // Throwable.addSuppressed(Throwable)
81: goto 88
84: aload_0
85: invokevirtual #9 // InputStream.close()
88: aload_3
89: athrow
90: return
Exception table:
from to target type
29 33 36 Class java/lang/Throwable
6 21 52 Class java/lang/Throwable
6 21 57 any
66 70 73 Class java/lang/Throwable
52 58 57 any
Note that the innocent 4 lines of Java exploded into 90 bytecodes. We should write short methods to make it easier for the HotSpot profilers to optimize our code. However, the profilers do not count lines of Java code, but rather the number of bytecodes our methods take. This is quite large.
Things get worse if we rewrite the previous Java code to add the FileInputStream creation as another resource, as so:
try (InputStream io = getNullStream();
FileInputStream fi = new FileInputStream("somefile.bin")) {
io.available();
}
This subtle change means that we first need to close fi and
then attempt to close io. The equivalent plain Java code
now looks like this, generating 170 bytecodes.
InputStream io = getNullStream();
Throwable throwable1 = null;
try {
FileInputStream fi = new FileInputStream("somefile.bin");
Throwable throwable2 = null;
try {
io.available();
} catch (Throwable t) {
throwable2 = t;
throw t;
} finally {
if (fi != null) {
if (throwable2 != null) {
try {
fi.close();
} catch (Throwable t) {
throwable2.addSuppressed(t);
}
} else {
fi.close();
}
}
}
} catch (Throwable t) {
throwable1 = t;
throw t;
} finally {
if (io != null) {
if (throwable1 != null) {
try {
io.close();
} catch (Throwable t) {
throwable1.addSuppressed(t);
}
} else {
io.close();
}
}
}
Aren't you glad we do not have to code that by hand?
Whilst my initial quest was to discover how the plain Java
code would look, I noticed that things had changed somewhat
since Java 9. Our first non-nested try-with-resource
remained the same. However, our second try-with-resource,
with multiple resources defined in the try(), now contained
a synthetic method to manage the closing.
Here is our code since Java 9, resulting in 80 bytecodes:
InputStream io = getNullStream();
Throwable throwable1 = null;
try {
FileInputStream fi = new FileInputStream("somefile.bin");
Throwable throwable2 = null;
try {
io.available();
} catch (Throwable t) {
throwable2 = t;
throw t;
} finally {
$closeResource(throwable2, fi);
}
} catch (Throwable t) {
throwable1 = t;
throw t;
} finally {
if (io != null) {
$closeResource(throwable1, io);
}
}
The $closeResource(Throwable, Autocloseable)
method appears magically inside our class and looks something like:
private static void $closeResource(Throwable t, AutoCloseable a) {
if (t != null) {
try {
a.close();
} catch(Throwable t2) {
throwable.addSuppressed(t2);
}
} else {
a.close();
}
}
One surprise is that the method $closeResource() does not
declare any exceptions thrown, even though a.close() clearly
could throw any Exception. This is again proof that the
concept of "checked exception" is a compile-time check, rather
than during execution. I hesitate to say "at runtime", because
the name "RuntimeException" hails from java.lang.Runtime, as
in, the representative class of the Java Runtime
Environment. During execution, there is no difference
between types of Throwable subclasses, whether they be unchecked
(subclasses of Error or RuntimeException) or checked (all
other Throwables). Why does it work? Well, the $closeResource()
method is synthetically added to the class and so the normal
javac compiler does not need to compile it. Magic.
There is another small change since Java 9 concerning try-with-resource. This code does not compile in Java 8:
import java.io.*;
public class Java9TryWithResource {
public static void main(String... args) throws IOException {
printClose(new FileInputStream("Java9TryWithResource.java"));
}
private static void printClose(InputStream in)
throws IOException {
try (in; // <-- Compiler error prior to Java 9
BufferedReader reader = new BufferedReader(
new InputStreamReader(in)
)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
}
Prior to Java 9, we would have had to write the try() like this:
try (InputStream temp = in;
BufferedReader reader = new BufferedReader(
new InputStreamReader(in)
)) {
I have not found much use for this feature yet. A word of warning. In the current version of IntelliJ 2018.1.2, things go horribly wrong when you try to migrate to the try-with-resource blocks. Previous versions were not as fragile IIRC. As always, whenever you migrate or refactor code, be very careful, because the tools do not always transform our code with semantic or even syntactic equivalence.
Kind regards from 30000 feet, en route to Duesseldorf, Germany
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.