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 Throwable
s). 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.