📜 ⬆️ ⬇️

Correctly free up resources in java

Improper resource release is one of the most frequently committed errors among Java programmers. By resource in this article, I will mean everything that implements the java.io.Closeable interface. So, immediately to the point.

We will consider the example of the OutputStream . Task: get the OutputStream as input, do some useful work with it, close the OutputStream .

Wrong decision number 1


 OutputStream stream = openOutputStream(); // -   stream stream.close(); 


This solution is dangerous, because if an exception is thrown in the code, stream.close () will not be called. A resource leak will occur (the connection will not close, the file descriptor will not be released, etc.)
')

Wrong decision number 2


Let's try to fix the previous code. We use try-finally :

 OutputStream stream = openOutputStream(); try { // -   stream } finally { stream.close(); } 


Now close() will always be called (for finally ): the resource will be freed anyway. It seems everything is correct. It is so?

Not.

The problem is as follows. The close() method can throw an exception. And if the main code for working with the resource also throws an exception, it will be overwritten with an exception from close() . The information about the original error disappears: we will never know what caused the original exception.

Wrong decision number 3


Let's try to correct the situation. If stream.close() can wipe out the “main” exception, then let's just “swallow” the exception from close() :

 OutputStream stream = openOutputStream(); try { // -   stream } finally { try { stream.close(); } catch (Throwable unused) { //  } } 


Now everything seems fine. We can go drink tea.

As if not so. This solution is even worse than the previous one. Why?

Because we just took and swallowed an exception from close() . Suppose that outputStream is a FileOutputStream wrapped in a BufferedOutputStream . Since BufferedOutputStream does flush() on the underlying stream in chunks, there is a possibility that it will call it during a call to close() . Now imagine that the file we are writing to is locked. Then the close() method will throw an IOException that will be successfully “eaten”. Not a single byte of user data was written to the file, and we did not know anything about it. Information is lost.

If we compare this decision with the previous one, then at least we will find out that something bad has happened. Here, all information about the error disappears.

Note: if the InputStream used instead of the OutputStream , then such code has the right to life. The fact is that if an exception is thrown in InputStream.close() , then (most likely) there will be no bad consequences, since we already considered everything we wanted from this stream. This means that InputStream and OutputStream have completely different semantics.

Imperfect solution


So, how does the resource processing code look right?

We need to take into account that if the main code throws an exception, this exception should have a higher priority than the one that can be thrown by the close() method. It looks like this:

 OutputStream stream = openOutputStream(); Throwable mainThrowable = null; try { // -   stream } catch (Throwable t) { //   mainThrowable = t; //      throw t; } finally { if (mainThrowable == null) { //    .   close() stream.close(); } else { try { stream.close(); } catch (Throwable unused) { // ,      //     ( ) } } } 


The disadvantages of this solution are obvious: cumbersome and difficult. In addition, information about the exception from close() disappears if the main code throws an exception. Also openOutputStream() can return null , and then a NullPointerException will NullPointerException (solved by adding another if'a, which leads to even more cumbersome code). Finally, if we have two resources (for example, InputStream and OutputStream ) and more, then the code will simply be unbearably complex.

The correct solution (Java 7)


Java 7 introduced the try-with-resources construct. We use it:

 try (OutputStream stream = openOutputStream()) { // -   stream } 


And that's all.

If an exception is thrown in the main code and in the close() method, the first exception will be prioritized, and the second exception will be suppressed, but information about it will be saved (using the Throwable.addSuppressed(Throwable exception) method, which is implicitly called by the Java compiler):

 Exception in thread "main" java.lang.RuntimeException: Main exception at A$1.write(A.java:16) at A.doSomething(A.java:27) at A.main(A.java:8) Suppressed: java.lang.RuntimeException: Exception on close() at A$1.close(A.java:21) at A.main(A.java:9) 


The right decision (Java 6 using Google Guava)


In Java 6, the standard library alone is indispensable. However, the great Google Guava library comes to our rescue. In Guava 14.0, the class com.google.common.io.Closer ( try-with-resources for the poor) appeared, with which you can simplify the non-ideal solution above:

 Closer closer = Closer.create(); try { OutputStream stream = closer.register(openOutputStream()); // -   stream } catch (Throwable e) { //     (  Error') throw closer.rethrow(e); } finally { closer.close(); } 


The solution is much longer than in the case of Java 7, but still much shorter than the non-ideal solution. The output will be about the same as Java 7.

Closer also supports an arbitrary amount of resources in it (the register(...) method). Unfortunately, Closer is a class marked with the @Beta annotation, which means it may undergo significant changes in future versions of the library (up to removal).

findings


Properly freeing resources is not as easy as it sounds (just in Java 7 only). Always pay proper attention to this. InputStream and OutputStream ( Reader and Writer ) are handled differently (at least in Java 6)!

Additions / corrections are welcome!

Next time I plan to tell you how to deal with a NullPointerException .

Source: https://habr.com/ru/post/178405/


All Articles