OutOfMemory: Why occurs and How to Fix it

Victor Osório
2 min readJun 7, 2019

--

This is not a common exception of a Java developer. I trust in Garbage Collector, but sometime it does not works. Let’s see why…

The Context

I had written an engine using a custom ClassLoader. To do that just create a new URLClassLoader, load a jar and execute what code you want, than close the URLClassLoader. If the new ClassLoader doesn’t have access to the current ClassLoader, the loaded classes does not have access to your code. Good! Very Good!

URL[] jars = new URL[] { /* The jars to be loaded */ };
try (URLClassLoader classLoader = new URLClassLoader(jars)) {
Job job = classLoader.loadClass(Job.class.getName());
job.execute();
}

This problem can occurs when you have an application inside an Application Server. Because, Applications Servers, like Wildfly, works as my engine. Every deployed application has it owns ClassLoaders.

The Problem

But… We do not have control with the code loaded from an external Jar. That is the problem. So if you want to avoid memory leaks the your custom classloader should be released by Garbage Collector. If the loaded code uses JDBC you will soon get an OutOfMemoryError and everything will crash! Not good!

The reason

Whe JDBC loads the driver they assumes that you will use only one ClassLoader, so it create a reference to your ClassLoader. This reference prevents the Garbage Collector from removing your custom ClassLoader from the memory.

So, even if you create your ClassLoader, execute your code and close it. The total of loaded classes will never decrease.

How to solve?

To solve this problem we have to unregister all JDBC drivers just after the execution, than close URLClassLoader.

URL[] jars = new URL[] { /* The jars to be loaded */ };
try (URLClassLoader classLoader = new URLClassLoader(jars)) {
Job job = classLoader.loadClass(Job.class.getName());
job.execute();
Collections.list(DriverManager.getDrivers())
.forEach(driver -> {
try {
DriverManager.deregisterDriver(driver);
} catch (SQLException e) {
logger.error(“Error unregistering driver!”, e);
}
});
}

Now, let’s look for the loaded classes:

This should be done every time your ClassLoader is released. Inside Java EE Applications running on Applications Server, you can add a Shutdown Listener. This can be easily done with CDI Events:

@ApplicationScoped
public class ApplicationLifecycle {
public void init(@Observes @Initialized(ApplicationScoped.class) Object init) {
// Application loaded
}
public void destroy(@Observes @Destroyed(ApplicationScoped.class) Object init) {
// Application unloaded
}
}

Possible problems

1. If the executed code create any Thread, the URLClassLoader is not eligible for Garbage Collector.
2. If some library create any Thread, same problem from #1.
3. MongoDb client create a Thread. 😫

--

--

Victor Osório
Victor Osório

Written by Victor Osório

Senior Software Engineer@Openet | Java | Software Architect | Technology | Society

No responses yet