Memory leaks

Memory leaks occur when new memory is allocated dynamically and never deallocated. Sometimes this may not be a problem for the user for a while. But when the memory leak has accumulated to a critical size, it leads to a slowdown in performance or to crush.

The first and most obvious reason for a memory leak is to forget about freeing memory. Although, tools like the C++ smart pointers and the Java garbage collector try to prevent memory leaks, you still have ways to make it.

cycled references

Let's we have a cycle in references such A->B->A. In this case the reference counts of the smart pointers will never drop to zero. Both objects A and B will be kept in memory.

#include <stdio.h>
#include <memory>

using namespace std;

class Cls {
public:
   Cls() {/* ... */}
   ~Cls() {/* ... */}

   void setSharedPointer(shared_ptr<Cls> p) {pCls = p;}

private:
   shared_ptr<Cls> pCls;
};

int main(int argc, char ** argv) {
   shared_ptr<Cls> objA(new Cls);
   shared_ptr<Cls> objB(new Cls);

   objA->setSharedPointer(objB);
   objB->setSharedPointer(objA);

   return 0;
}

nested classes

In Java, nested non-static classes, including anonymous objects, have an implicit reference to the parent object. Therefore, passing such objects outside of the parent object may cause a memory leak.

public class NestedLeak {

    public static ArrayList<Object> cache = new ArrayList<Object>();

    public static class Parent {
        char chrs[] = new char[2000];
Object getChild() { return new Child(); } // nested non-static class private class Child { }; } // After garbage collection, the parent object will be kept in memory. // Only after removing child object from map, // parent object may be removed from memory. public void leak() { Parent p = new Parent(); // move the child outside of the parent cache.add(p.getChild()); } public static void main(String[] args) { NestedLeak nl = new NestedLeak(); nl.leak(); ... // if no other references to child object, // parent object will be removed from memory cache.clear(); System.gc(); } }

exit from context

Suppose the user opens a window. At this moment a lot of objects will be created, such as GUI elements, adapters, handlers, etc. All these objects are related and exist in the same environment - a window context.

When one of these objects exits the context, you risk saving the entire environment in memory after the user closes the window.

For example, Android will recreate the window (activity) after the device changes orientation. Let developer decided to cache background image as static data. After device rotated both windows will be in memory. First window is in which image was created. And second window, which is a visible to the user. This was happened, because Drawable also linked with context.

private static Drawable sBackground;
// ... 
@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);
  
  TextView label = new TextView(this);
  label.setText("memory leak");
  
  // save image as static data
  if (sBackground == null) {
    bgTextView = getDrawable(R.drawable.mybitmap);
  }
  
  label.setBackgroundDrawable(bgTextView);
  setContentView(label);
}

equal() and hashCode() methods

By default, the hash code is calculated based on the internal identifier of the object, in most cases this is sufficient. But if the key object is often recreated with the same parameters, then on a subconscious level, the illusion of using the same key may arise. But in fact, each object has its own identifier.

Wrong implementation of equal() and hashCode() methods also leads to memory leak. This happens when the hash code value depends on a mutable member. It seems that we use the same object as a key, but in fact, a new key/value pair will be generated.

import java.util.HashMap;

public class LeakDemo {

    public final HashMap<Key1, Object> map1 = 
        new HashMap<LeakDemo.Key1, Object>();
    
    public final HashMap<Key2, Object> map2 = 
        new HashMap<LeakDemo.Key2, Object>();
public static class Key1 { public String s; public Key1(String s) { this.s = s; } } public static class Key2 { public String s; public Key2(String s) { this.s = s; } @Override public int hashCode() { return s.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof Key2) { return ((Key2) obj).s.equals(s); } return false; } } public static void main(String[] args) { LeakDemo leak = new LeakDemo(); leak.map1.put(new Key1("key"), "Hello World1"); leak.map1.put(new Key1("key"), "Hello World2"); // in result we have two elements System.out.println("map1 size =" + leak.map1.size()); leak.map2.put(new Key2("key"), "Hello World1"); leak.map2.put(new Key2("key"), "Hello World2"); // one element System.out.println("map2 size =" + leak.map2.size()); leak.map2.clear(); // in result we have two elements Key2 key = new Key2("key"); leak.map2.put(key, "Hello World1"); key.s = "same object, but actualy other key"; leak.map2.put(key, "Hello World1"); System.out.println("map2 size =" + leak.map2.size()); } }

JNI

The leak can occur when using JNI. Java objects created by native code live as long as the method that created them is executed. But in some cases you need to save the object, and one way is to create a global reference (a term in JNI). And such objects are destroyed only by explicitly native code. The garbage collector does not process them. Such a leak can only be noticed by looking at the Java heap dump.