onseok

How Kotlin's use Function Prevents Resource Leaks

When developing a custom gallery feature, there is a common mistake made during the image thumbnail creation process. BitmapFactory.decodeStream() does not automatically close the stream after decoding an InputStream into a Bitmap, so the developer must explicitly close the FileInputStream. This step is frequently overlooked.

When a user scrolls quickly, hundreds of streams can remain open, exhausting the file descriptors. Android typically has a limit of 1024 file descriptors per process, and exceeding this limit causes the app to crash. To prevent this problem, you should use Kotlin’s use function.

At first glance, use appears simple – it is a function that automatically closes resources. However, when you dig into the actual implementation, you will find that within roughly 30 lines of code, there are carefully considered solutions for exception handling, performance optimization, and overcoming JVM limitations.

Why Is Resource Management So Difficult?

The JVM’s garbage collector handles memory cleanup automatically, but it does not manage OS resources like file handles or sockets. The operating system limits the number of files a process can have open simultaneously – macOS defaults to 256, and Linux typically allows 1024. If a web server opens a file for every request without closing them, it will quickly hit this limit.

macOS 15.6.1 24G90 arm64

You also need to be careful when reading media files with ContentResolver. If you fail to properly close the stream obtained from contentResolver.openInputStream(), the issue may not surface on an emulator with few files. However, when scanning thousands of photos on a real device, the likelihood of encountering a Too many open files error becomes extremely high!

Relying on Java’s finalize() is not the answer either. finalize() has unpredictable execution timing, significantly degrades GC performance, makes exception handling complex, and even allows dangerous behaviors like object resurrection. Due to these problems, it was deprecated starting with Java 9, and according to OpenJDK’s JEP 421, it is slated for complete removal.

Java’s Approach

To address these problems, Java 7 introduced the try-with-resources construct. However, the syntax remained verbose, and the bigger issue was its exception handling behavior. When both the business logic and the resource cleanup process throw exceptions, which exception should be surfaced?

If you simply call close() in a finally block, a serious problem arises: the original exception is lost. This makes it impossible to find the real cause during debugging.

Suppressed Exception?

Java 7 solved this problem with the suppressed exception mechanism, and Kotlin’s use takes full advantage of it. The approach preserves the original exception while attaching the exception from close() as a suppressed exception.

Here is how the actual implementation looks:

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e  // Save the original exception
        throw e
    } finally {
        this.closeFinally(exception)
    }
}

internal fun Closeable?.closeFinally(cause: Throwable?) = when {
    this == null -> {}
    cause == null -> close()  // Normal path
    else -> try {
        close()
    } catch (closeException: Throwable) {
        cause.addSuppressed(closeException)  // The key part!
    }
}

Now when you inspect the stack trace, you can see both exceptions. The root cause is clear, and secondary issues are not missed. In practice, this information can be invaluable when debugging production issues.

Closing Thoughts

Resource leaks tend to be invisible in development environments but surface in production. They are hard to find and even harder to fix. When working with objects that implement Closeable or AutoCloseable – especially in Android development where you deal with resources like Stream, Cursor, and Bitmap – wrapping them with use is recommended. Making use a habit can effectively prevent crashes caused by resource leaks.

References

#kotlin #use #resource