JAVA References And Garbage Collection

Different Java references

Many java developers are not aware that java has mainly 4 types of references.
1.       Strong Reference
2.       Weak Reference.
3.       Soft Reference.
4.       Phantom Reference.


But why there are different types of reference? What will be there usage?

To understand that I will take an Example,

Suppose in an Application, frequently one needs to fetch data from a MASTER TABLE. As a clever developer certainly he/she don’t want every time application will hit the database, it will degrade the performance.
Obviously, the choice is Cache. Cache is a class (Internally a Map) ,first application will hit the cache to check data is available there else hit the database and put the entry in cache so next time it can be found in the cache to skip database call.

Is it going to improve the performance?
It will depend on the situation, If Master table has less entry this will work fine and certainly increase the performance. But if Master Table has huge entries it will create a problem as a time to time Cache map is growing and load entries from Master table. Now instead of providing better performance it will be the cause of Out of memory. Imagine a situation where all rows in this huge master table have been loaded to cache.  Think about the size of cache it will take most of the JVM memory even cause Out of memory.

So what should we do?
One thing we can do restricts the number of entries in the Cache. And delete the old entries from the cache.  I don’t go to the implementation part ,think is it going to solve the problem?

It will partially solve the problem but it will take constant memory in JVM. Although some of the Object in the cache is not used for a long time.

What will be the Ideal solution?
The Ideal solution would be if we can make such type of cache which is dynamic in nature it will growing and shrinking as per need.
So we need some kind of technique where we can delete those entries which will not use for a long time and sits in cache.

To achieve it , Java provides different type of reference in java.lang.ref package.

Strong Reference:  We use Strong reference in java everywhere.  Create an object then assigns it to a reference. Note that if object has a strong reference this object is never be garbage collected.
Example:
HelloWorld hello = new HelloWorld();
Here hello is strong reference to HelloWorld Object.

Soft Reference:  If an Object has no Strong reference but has a Soft Reference then garbage collector reclaims this Object’s memory when there is not enough memory, and garbage collector needs to freed up some memory. In spite of soft reference garbage collector reclaims this Object’s memory. To get a Strong reference from soft reference one can invoke get() method. If the Object is not garbage collected it returns the object else return null.

Weak Reference: If an Object has no Strong reference but has a Weak Reference then garbage collector reclaims this Object’s memory although there is enough memory. To get a Strong reference from soft reference one can invoke get() method. If the Object is not garbage collected it return the object else return null.

Phantom Reference: If an Object does not have aforesaid references then may it has phantom references. Phantom references can’t be accessed directly. Using get() method it always returns null. So what will be use of such reference? Think an object can referred by a strong reference in finalize block so to garbage collect this Object need second time to run the GC. But by phantom reference only one GC execution will suffice to GCed the same.

Example of different type of reference.

package com.example.reference;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

public class ReferenceExample {
      
       private String status ="Hi I am active";
      
      
      
       public String getStatus() {
              return status;
       }



       public void setStatus(String status) {
              this.status = status;
       }
      
      



       @Override
       public String toString() {
              return "ReferenceExample [status=" + status + "]";
       }



       public void strongReference()
       {
              ReferenceExample ex = new ReferenceExample();
              System.out.println(ex);
       }
      
       public void softReference()
       {
              SoftReference<ReferenceExample> ex = new SoftReference<ReferenceExample>(getRefrence());
              System.out.println("Soft refrence :: " + ex.get());
       }
      
       public void weakReference()
       {
              int counter=0;
              WeakReference<ReferenceExample> ex = new WeakReference<ReferenceExample>(getRefrence());
              while(ex.get()!=null)
              {
                     counter++;
                     System.gc();
                     System.out.println("Weak reference deleted  after:: " + counter + ex.get());
              }
             
       }
      
       public void phantomReference() throws InterruptedException
       {
              final ReferenceQueue queue = new ReferenceQueue();
              PhantomReference<ReferenceExample> ex = new PhantomReference<ReferenceExample>(getRefrence(),queue);
              System.gc();
              queue.remove();
              System.out.println("Phantom reference deleted  after");
             
             
       }
      
      
      
       private ReferenceExample getRefrence()
       {
              return new ReferenceExample();
       }
      
       public static void main(String[] args) {
              ReferenceExample ex = new ReferenceExample();
              ex.strongReference();
              ex.softReference();
              ex.weakReference();
              try {
                     ex.phantomReference();
              } catch (InterruptedException e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
              }
       }
      

}


Output :
ReferenceExample [status=Hi I am active]
Soft refrence :: ReferenceExample [status=Hi I am active]
Weak reference deleted  after:: 1null
Phantom reference deleted  after



Look at , softReference() method here we create a soft reference as memory is available so reference is not Gced.
For weakRefrence(), reference is GCed immediately as no strong reference is there.

Same for Phantom reference.


Comparison:


Memory Leak and JAVA Code

Misuse of java code

Java implicitly reclaims memory by GC (a daemon thread). GC periodically checks, if there is any object which is unreachable or to be precise no reference pointing to the same.  If so GC reclaims it's acquired memory.

Now the question is should we worry about memory Leak or java handles it?

Pay attention to the definition,
 An Object is eligible for garbage collection when it is unreachable(unused), no living thread can reach it.
So if an object which is not used in an application but unintentionally it has references, same is not eligible for garbage collection, a potential Memory Leak.

GC takes care of unreachable objects, but can’t determine unused objects, Unused Object depends on Application logic so coder must have to pay attention to the business code. Silly mistakes silently grew up as MONSTER.

Memory leaks can occur in many ways, I will take some of the examples.

Example 1 :  Autoboxing
package com.example.memoryleak;

public class Adder {
      
       public long addIncremental(long l)
       {
              Long sum=0L;
               sum=sum+l;
             
               return sum;
             
       }
      
       public static void main(String[] args) {
              Adder adder = new Adder();
              long totsum =0;
              for(long i=0;i<1000;i++)
              {
                     adder.addIncremental(i);
              }
               
       }

}

Can you spot the memory Leak?
Here, I made a mistake instead of taking primitive long for the sum I took Long(wrapper class) which is the cause of memory leak.  Due to auto-boxing, sum=sum+l; creates new Object in every iteration so 1000 unnecessary Objects will be created. Please avoid mix and match between primitives and wrapper class. Try to use primitive as long as you can.


Example 2: Using cache
package com.example.memoryleak;

import java.util.HashMap;
import java.util.Map;

public class Cache {
      
       private Map<String,String> map= new HashMap<String,String>();
      
       public void initCache()
       {
              map.put("Anil", "Work as Engineer");
              map.put("Shamik", "Work as Java Engineer");
              map.put("Ram", "Work as Doctor");
             
       }
      
       public Map<String,String> getCache()
       {
              return map;
       }
      
       public void forEachDisplay()
       {
              for(String key : map.keySet())
              {
                     String val = map.get(key);
                    
                     System.out.println(key + " :: "+ val);
              }
       }
      
      
       public static void main(String[] args) {
             
              Cache cache = new Cache();
              cache.initCache();
              cache.forEachDisplay();
             
             
       }
      
      

}


Here memory leak occurs due to the internal map data structure. This class is for display employee value from the cache. Once those are displayed, further there is no need to store those elements in the cache.
We forgot to clear the cache so although objects in cache are not required anymore by the application ,but in can’t be GCed as map holds a strong reference to them.
So when you using your own Cache don’t forget to clear them if items in the cache are no longer requires. Alternatively, you can initialize cache by WeakHashMap, the beauty of WeakHashMap is, if keys are not referenced by any other objects that entry will be eligible for garbage collection.

There is lot say about WeakHashMap I will discuss it in another article. But use same with caution, if you want to reuse the values stored in the cache, it may happen its key is not referenced by any other object so entry will be GCed and that value magically disappears.

Example 3: Closing connection

try
{
  Connection con = DriverManager.getConnection();
 …………………..
con.close();
}
Catch(exception ex)
{
}

In above example, we close the connection(Costly) resource in the try block, so in the case of an exception , the connection will not be closed. So it creates a memory leak as this connection never return back to the pool.
Please always put any closing stuff in the finally block.

Example 4: Using CustomKey
package com.example.memoryleak;

import java.util.HashMap;
import java.util.Map;

public class CustomKey {
      
       public CustomKey(String name)
       {
              this.name=name;
       }
       private String name;
      

       public static void main(String[] args) {
             
              Map<CustomKey,String> map = new HashMap<CustomKey,String>();
              map.put(new CustomKey("Shamik"), "Shamik Mitra");
              String val = map.get(new CustomKey("Shamik"));
              System.out.println("Missing equals and hascode so value is not accessible from Map " + val);
             
       }
}


As in CustomKey we forgot to provide equals() and hashcode() implementation so a key and value once stores in map can’t be retrieved later, as map get() method checks hashcode() and equals(). But this entry is not able to GCed as the map has a reference to it but application can’t access it. Surely a memory leak.

So when make your Custom key always provide a equals and hashcode() implementation.

Example 5: Mutable Custom Key

package com.example.memoryleak;

import java.util.HashMap;
import java.util.Map;

public class MutableCustomKey {
      
       public MutableCustomKey(String name)
       {
              this.name=name;
       }
       private String name;
      
      
      
      

       public String getName() {
              return name;
       }





       public void setName(String name) {
              this.name = name;
       }





       @Override
       public int hashCode() {
              final int prime = 31;
              int result = 1;
              result = prime * result + ((name == null) ? 0 : name.hashCode());
              return result;
       }





       @Override
       public boolean equals(Object obj) {
              if (this == obj)
                     return true;
              if (obj == null)
                     return false;
              if (getClass() != obj.getClass())
                     return false;
              MutableCustomKey other = (MutableCustomKey) obj;
              if (name == null) {
                     if (other.name != null)
                           return false;
              } else if (!name.equals(other.name))
                     return false;
              return true;
       }





       public static void main(String[] args) {
             
              MutableCustomKey key = new MutableCustomKey("Shamik");
             
              Map<MutableCustomKey,String> map = new HashMap<MutableCustomKey,String>();
              map.put(key, "Shamik Mitra");
              MutableCustomKey refKey = new MutableCustomKey("Shamik");
              String val = map.get(refKey);
              System.out.println("Value Found " + val);
              key.setName("Bubun");
              String val1 = map.get(refKey);
              System.out.println("Due to MutableKey value not found " + val1);
             
       }

}







Although here we provide equals() and hashcode(), for custom Key but we make it mutable so if unintentionally after storing same into the map, if it’s property is changed that entry will never found by the application but map hold a reference so memory leak happens.

Always make your custom key immutable.


Example 6: Internal Data Structure



package com.example.memoryleak;

public class Stack {

       private int maxSize;
       private Integer[] stackArray;
       private int pointer;

       public Stack(int s) {
              maxSize = s;
              stackArray = new int[maxSize];
              pointer = -1;
       }

       public void push(int j) {
              stackArray[++pointer] = j;
       }

       public int pop() {
              return stackArray[pointer--];
       }

       public int peek() {
              return stackArray[pointer];
       }

       public boolean isEmpty() {
              return (pointer == -1);
       }

       public boolean isFull() {
              return (pointer == maxSize - 1);
       }
      
       public static void main(String[] args) {
             
              Stack stack = new Stack(1000);
             
              for(Integer i=0;i<1000;i++)
              {
                     stack.push(i);
              }
             

              for(Integer i=0;i<1000;i++)
              {
                     Integer element = stack.pop();
                     System.out.println("Poped element is "+ element);
              }
             
       }

}


Here we face a tricky problem when stack first grows then shrinks. Actually, it is due to the internal implementation. Stack internally holds an array but for application perspective, the active portion of Stack is where the pointer is pointing.

So when stack grows to 1000, internally array cells are fill up with elements but afterwards when we popped all elements, pointer comes to zero so as per application it is empty but still internal array contains all popped references. In java, we call it Obsolete reference. An obsolete reference is such a reference which can’t be dereferenced.

And this reference can’t be GCed as array holds those elements, but they are unnecessary after popping.

To fix it , we need to set null value when the pop action occurs so those objects are able to garbage collection.


public int pop() {
              int size = pointer--
              Integer element= stackArray[size];
              stackArray[size]=null;
              return element;

       }








Safety measure for preventing memory Leak:
Conclusion: Due to silly mistakes memory leaks happen so when coding please review your code to prevent the memory leak.