Last post we introduced the concept of memory leaks, what causes them and how to find and fix them. This post we’ll examine some code that can cause memory leaks.
Causes of Memory Leaks
Memory leaks can be caused by many different coding issues. These include the following:
- The use of large static fields.
- Unclosed file resources.
- Incorrect or missing
equals()
andhashCode()
implementations. - The creation of very large objects.
- The use of anonymous classes.
- The use of finalizers.
- Insertion into
Collection
objects without deletion. - Unbounded caches.
- Interned strings.
- Lots of large session objects in a web application.
- Listener methods that aren’t deregistered.
What Does The Static Modifier Do?
When we declare a field to be static, it means that the field belongs to the class itself, rather than to an instance of the class. This means that there is only one copy of the field in memory, regardless of how many instances of the class exist. In the same way, a static method belongs to the class itself. A static method cannot refer to any instance fields.
We must be aware of the effects in our code when using the static
modifier.
Code that uses a lot of static variables and methods can be harder to test. This can introduce hidden dependencies between different parts of the program. It becomes more difficult to determine how changes to one part of our code might affect other parts. This can lead to inflexible code that is more difficult to change.
Static variables can lead to concurrency problems if multiple threads access and modify the same variable at the same time.
If a static variable is not properly released when it is no longer needed, it can lead to memory leaks and other performance issues.
Static Variable Examples
Static variables in Java can lead to memory leaks. They have a lifetime that is usually the same as the lifetime of the running application. Unless we explicitly set them to null
, they live as long as the program runs. Forgetting to do this can cause memory leaks.
Take a look at the following code:
import java.util.*;
public class SomeClass {
private static List<Integer> list = new ArrayList<>();
public static void fillList(int number) {
Random r = new Random();
for (int i=0; i<number; ++i) {
list.add(r.nextInt());
}
}
} // end of class
We can call the fillList()
method as follows:
SomeClass.fillList(2_000_000);
The static list
field belongs to the SomeClass
class instance. After the fillList()
method is called on the class, the list will contain two million Integer
objects. These objects will never be eligible for garbage collection until the SomeClass
class is unloaded. This will usually only happen when the application ends.
Let’s take out the two static
keywords. The code now becomes:
import java.util.*;
public class SomeClass {
private List<Integer> list = new ArrayList<>();
public void fillList(int number) {
Random r = new Random();
for (int i=0; i<number; ++i) {
list.add(r.nextInt());
}
}
} // end of class
We’ve now declared the list
field without the static
keyword. This means there is a list
field for each SomeClass
object. The fillList()
method is now called on an object as follows:
SomeClass object = new SomeClass();
object.fillList(2_000_000);
When the SomeClass
object is not longer in use and is dereferenced, the list
field will be eligible for garbage collection. The two million Integer
objects can then be removed from the heap.
Heavy use of static variables can potentially cause memory leaks. Every object that is reachable from a static variable can also potentially live forever.
As a side note, whether we declared the fillList()
method as static or not is immaterial. The method operates the same way regardless. The way we invoke it will be different. The memory leak doesn’t happen in the method code, but with the list
variable.
Leak or No Leak?
We only consider something to be a memory leak if we wanted the memory to be freed and it wasn’t. Let’s assume that we only want our static variable to reference an object for a part of the application runtime. If we forget to set it to null
when we’re done with that object, we will likely end up with a memory leak.
But if we intend the static variable to reference an object for as long as the program is running, then it is most definitely not a leak. It is more likely to be a “permanent” singleton. If the object was reclaimed while we needed it to be available, that would have been even worse than a memory leak.
Insertion into Collections without Deletion
We need to be alert when we use collections in general. It’s very easy to unintentionally hold on to references for longer than we need to.
As mentioned earlier, adding objects to collections without deleting them is a common source of memory leaks. Often we forget to remove objects from a collection when we don’t need them any more. These unneeded objects are still being referenced, so the garbage collector can’t remove them.
Instead of directly referencing objects, we can use special reference objects that allow easy garbage collection. These reference objects are the SoftReference
and WeakReference
classes in the java.lang.ref
package. These will be the topic of a future post.
Anonymous Inner Classes and Listeners
We often write GUI event listeners as anonymous inner classes. These are non-static inner classes. By default, a non-static inner class holds an implicit reference to its containing class. If we use this inner class object in our application, then even after our containing class object goes out of scope, it won’t be garbage collected.
As long as the listener is reachable it will continue to refer to the whole class. If we register an object as a listener and forget to deregister it, it can end up holding a reference to the outer class object. This object cannot be garbage collected, and a memory leak occurs.
If the inner class doesn’t need access to the containing class members, we should change it into a static class.
This is usually not a problem when we add listeners to components in a frame (AWT’s Frame
or Swing’s JFrame
). When that frame is disposed and is no longer referenced, all its components become unreachable. Thereafter everything will be garbage collected.
Conclusion
We’ll look at some more of the causes of memory leaks in the next post. These will include incorrect or missing equals()
and hashCode()
methods, and interning Strings.
Until then, stay healthy and keep learning!