An obsolete reference is one that is kept around but will never be used, preventing the object it refers to from being eligible for garbage collection, thus causing a memory leak.

Manually Setting to NULL
Nulling out a reference to remove obsolete references to an object is good, but one must not overdo it. The best way to eliminate an obsolete reference is to reuse the variable in which it was contained or to let it fall out of scope.

Lets take a Simple Stack implementation as in effective Java

public Object pop() {
    if (size == 0)
        throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null; // Eliminate obsolete reference
    return result;
}

In the above code you can see that the stack size is shrinked when ever a pop operation is carried out and is set to null allowing the garbage collector to access the unused space to reclaim

Using WeakHashMap
WeakHashMap is an implementation of the Map interface. WeakHashMap is almost same as HashMap except in case of WeakHashMap, if object is specified as key doesn’t contain any references- it is eligible for garbage collection even though it is associated with WeakHashMap. i.e Garbage Collector dominates over WeakHashMap.

How HashMap Works

// Java program to illustrate 
// Hashmap 
import java.util.*;
class HashMapDemo
{
    public static void main(String args[])throws Exception
    {
        HashMap m = new HashMap();
        Demo d = new Demo();
         
        // puts an entry into HashMap
        m.put(d," Hi "); 
         
        System.out.println(m); 
        d = null;
         
        // garbage collector is called
        System.gc();
         
        //thread sleeps for 4 sec
        Thread.sleep(4000); 
         
        System.out.println(m);
        }
    }
    class Demo
    {
        public String toString()
        {
            return "demo";
        }
         
        // finalize method
        public void finalize()
        {
            System.out.println("Finalize method is called");
        }
}

Output

{demo=Hi}
{demo=Hi}

How WeakHashMap Works

// Java program to illustrate 
// WeakHashmap 
import java.util.*;
class WeakHashMapDemo
{
    public static void main(String args[])throws Exception
    {
        WeakHashMap m = new WeakHashMap();
        Demo d = new Demo();
         
        // puts an entry into WeakHashMap
        m.put(d," Hi "); 
        System.out.println(m);
         
        d = null;
         
        // garbage collector is called
        System.gc(); 
         
        // thread sleeps for 4 sec
        Thread.sleep(4000); .
         
        System.out.println(m);
    }
}
 
class Demo
{
    public String toString()
    {
        return "demo";
    }
     
    // finalize method
    public void finalize()
    {
        System.out.println("finalize method is called");
    }
}

Output

{demo = Hi}
finalize method is called
{ }

There could not be any class which conform to both Same type comparison and Mixed type comparison

Same-Type Comparison
With such an implementation of equals() you can store an Employee(“Hanni Hanuta”) and a Student(“Hanni Hanuta”) into the same HashSet , but retrieval from the collection will not work as expected. You will not find any of these two contained objects when you ask the HashSet whether it contains a Person(“Hanni Hanuta”) , because all three object are unequal to each other.

Mixed-Type Comparison
In a class hierarchy, where Employee and Student are subclasses of a Person , representing roles of a person, it may make sense to compare an Employee to a Student to see whether they are the same Person . With an implementation of equals() that only allows same-type comparison an Employee and a Student would not be comparable.So mixed type comparison allows comparison between parent and child object.With this type of equals() implementation you will have problems storing an Employee(“Hanni Hanuta”) and a Student(“Hanni Hanuta”) in the same HashSet . The HashSet will reject the second add() operation, because the collection already contains an element that compares equal to the new element.

Lets see an example of Mixed-Type comparison

class BaseClass {
    private int field1 = 0;

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof BaseClass) {
            return field1 == ((BaseClass) obj).field1;
        }
        return false;
    }
}

class BadSubClass extends BaseClass {
    private int field2 = 0;

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof BadSubClass) {
            return super.equals(obj) 
                    && field2 == ((BadSubClass) obj).field2;
        }
        return false;
    }
}
BaseClass baseClass = new BaseClass();
BadSubClass subClass = new BadSubClass();

System.out.println(baseClass.equals(subClass)); // prints 'true'
System.out.println(subClass.equals(baseClass)); // prints 'false'

Now the above implementation does not comply to symmetric property of equals
x and y, x.equals(y) should return true if and only if y.equals(x) returns true.

The work around for this is

class BaseClass {
    private int field1 = 0;

    @Override
    public boolean equals(Object obj) {
        if (obj != null && obj.getClass() == getClass()) {
            return field1 == ((BaseClass) obj).field1;
        }
        return false;
    }
}

class GoodSubClass extends BaseClass {
    private int field2 = 0;

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof GoodSubClass) {
            return super.equals(obj) && field2 == ((GoodSubClass) obj).field2;
        }
        return false;
    }
}

Reusing Immutable Object

 
              //Dont Use this
              String strName = new String("Mugil");

              //Use this
              String strName = "Mugil";
         

The Best Example of Immutable Object Reuse is Integer Caching in Java.Lets take the following Example

public class Scratch
{
   public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;  //1
        System.out.println(a == b);

        Integer c = 100, d = 100;  //2
        System.out.println(c == d);
   }
}

Output

false
true

Integer class keeps a cache of Integer instances in the range of -128 to 127, and all autoboxing, literals and uses of Integer.valueOf() will return instances from that cache for the range it covers.

Note that the cache only works if you use auto-boxing or the static method Integer.valueOf(). Calling the constructor will always result in a new instance of integer, even if the value of that instance is in the -128 to 127 range. Integer.valueOf(int). It will return the same Integer object for inputs less than 256.

Reusing Mutable Object

package com.mugil.org.ej;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Item5 
{
	public static void main(String[] args) throws ParseException 
	{
		Person objPerson = new Person();
		objPerson.initializeDates();
		
		
		SimpleDateFormat sdf = new SimpleDateFormat("dd-M-yyyy");
		String endDate = "31-03-2019";
		Date financialYrEndDate = sdf.parse(endDate);
		
		if(financialYrEndDate.after(objPerson.getFinancialYrStartDate()))
		{
			System.out.println("Valid End Date");
		}
	}
}


class Person
{
	private Date financialYrStartDate;	
	
	public void initializeDates() throws ParseException
	{
		SimpleDateFormat sdf = new SimpleDateFormat("dd-M-yyyy");
		String dateInString = "01-04-2018";
		financialYrStartDate = sdf.parse(dateInString);
	}

	public Date getFinancialYrStartDate() {
		return financialYrStartDate;
	}

	public void setFinancialYrStartDate(Date financialYrStartDate) {
		this.financialYrStartDate = financialYrStartDate;
	}	
}

In the above example I know for Sure that the Financial Year End Date should be after Start Date and the Start Date is going to be same for Every Year

package com.mugil.org.ej;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Item5 
{
	public static void main(String[] args) throws ParseException 
	{			
		SimpleDateFormat sdf = new SimpleDateFormat("dd-M-yyyy");
		String endDate = "31-03-2019";
		Date financialYrEndDate = sdf.parse(endDate);
		
		if(financialYrEndDate.after(Person.financialYrStartDate ))
		{
			System.out.println("Valid End Date");
		}
	}
}


class Person
{
	static Date financialYrStartDate;
	
	static
	{	
		SimpleDateFormat sdf = new SimpleDateFormat("dd-M-yyyy");
		String dateInString = "01-04-2018";		
		try {
			financialYrStartDate = sdf.parse(dateInString);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}		
}

Since financialYrStartDate is going to be same it is made as Class Variable which helps to prevent unnecessary Object Creation.

Use Primitives instead of Wrapper Class

public static void main(String[] args) {
    Long sum = 0L; // uses Long, not long
    for (long i = 0; i <= Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println(sum);
}

It takes 43 seconds to run as Long and long primitive brings it down to 6.8 seconds.

Noninstantiable classes are those which can be invoked using object creation.We prefer classes to be Noninstantiable if you want the class to be Utility class with static methods and variables in it.

public final class Useless {
    private Useless() {}
}

A private constructor is the normal object-oriented solution. However, it would still be possible to instantiate such a class using reflection, like this:

Constructor<Useless> con = Useless.class.getDeclaredConstructor();
con.setAccessible(true); // bypass "private"
Useless object = con.newInstance();

To prevent even reflection from working, throw an exception from the constructor:

public final class Useless {
    private Useless() {
        throw new UnsupportedOperationException();
    }
}

Dont Use absract class for NonInstantiable classes

Attempting to enforce noninstantiability by making a class abstract does not work. The class can be subclassed and the subclass instantiated. Furthermore, it misleads the user into thinking the class was designed for inheritance

While Implementing Singleton the following things should be answered

  1. Reflection
  2. Serialization
  3. Cloning

Objects for Singleton Classes implemented using private Constructor can be invoked by Reflection as below

Item3.java

package com.mugil.org.ej;

import java.lang.reflect.Constructor;

public class Item3 {
	public static void main(String[] args) 
	{
		// reflection concept to get constructor of a Singleton class.  
		Constructor<Singleton> constructor;
		
		try {			
			constructor = Singleton.class.getDeclaredConstructor();
			
			// change the accessibility of constructor for outside a class object creation.
			constructor.setAccessible(true);
			
			// creates object of a class as constructor is accessible now.
			Singleton secondOb = constructor.newInstance();
			System.out.println(secondOb.getName());
			
			// close the accessibility of a constructor.
			constructor.setAccessible(false);
		} catch (Exception e){
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}
}


class Singleton {

    private static Singleton instance = new Singleton();

    /* private constructor */
    private Singleton() {}

    public static Singleton getDefaultInstance() {
        return instance;
    }
    
    public String getName()
    {
    	return "MyName";
    }
}

Output

MyName

Singleton and Serialization
Without readResolve() Method

// Java code to explain effect of 
// Serilization on singleton classes
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
class Singleton implements Serializable 
{
    // public instance initialized when loading the class
    public static Singleton instance = new Singleton();
     
    private Singleton() 
    {
        // private constructor
    }
}
 
 
public class GFG 
{
 
    public static void main(String[] args) 
    {
        try
        {
            Singleton instance1 = Singleton.instance;
            ObjectOutput out
                = new ObjectOutputStream(new FileOutputStream("file.text"));
            out.writeObject(instance1);
            out.close();
     
            // deserailize from file to object
            ObjectInput in 
                = new ObjectInputStream(new FileInputStream("file.text"));
             
            Singleton instance2 = (Singleton) in.readObject();
            in.close();
     
            System.out.println("instance1 hashCode:- "
                                                 + instance1.hashCode());
            System.out.println("instance2 hashCode:- "
                                                 + instance2.hashCode());
        } 
         
        catch (Exception e) 
        {
            e.printStackTrace();
        }
    }
}

Output

instance1 hashCode:- 1550089733
instance2 hashCode:- 785945

With readResolve() Method

// Java code to remove the effect of 
// Serialization on singleton classes
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
class Singleton implements Serializable 
{
    // public instance initialized when loading the class
    public static Singleton instance = new Singleton();
     
    private Singleton() 
    {
        // private constructor
    }
     
    // implement readResolve method
    protected Object readResolve()
    {
        return instance;
    }
}
 
public class GFG 
{
 
    public static void main(String[] args) 
    {
        try
        {
            Singleton instance1 = Singleton.instance;
            ObjectOutput out 
                = new ObjectOutputStream(new FileOutputStream("file.text"));
            out.writeObject(instance1);
            out.close();
         
            // deserailize from file to object
            ObjectInput in 
                = new ObjectInputStream(new FileInputStream("file.text"));
            Singleton instance2 = (Singleton) in.readObject();
            in.close();
         
            System.out.println("instance1 hashCode:- "
                                           + instance1.hashCode());
            System.out.println("instance2 hashCode:- "
                                           + instance2.hashCode());
        } 
         
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

Output

instance1 hashCode:- 1550089733
instance2 hashCode:- 1550089733

Refer https://codethataint.com/blog/singleton-and-serialization/

// JAVA code to explain cloning 
// issue with singleton
class SuperClass implements Cloneable
{
  int i = 10;
 
  @Override
  protected Object clone() throws CloneNotSupportedException 
  {
    return super.clone();
  }
}
 
// Singleton class
class Singleton extends SuperClass
{
  // public instance initialized when loading the class
  public static Singleton instance = new Singleton();
 
  private Singleton() 
  {
    // private constructor
  }
}
 
public class GFG
{
  public static void main(String[] args) throws CloneNotSupportedException 
  {
    Singleton instance1 = Singleton.instance;
    Singleton instance2 = (Singleton) instance1.clone();
    System.out.println("instance1 hashCode:- "
                           + instance1.hashCode());
    System.out.println("instance2 hashCode:- "
                           + instance2.hashCode()); 
  }
}

Output

Output :- 
instance1 hashCode:- 366712642
instance2 hashCode:- 1829164700

Two different hashCode means there are 2 different objects of singleton class.

To overcome this issue, override clone() method and throw an exception from clone method that is CloneNotSupportedException. Now whenever user will try to create clone of singleton object, it will throw exception and hence our class remains singleton.

// JAVA code to explain overcome 
// cloning issue with singleton
class SuperClass implements Cloneable
{
  int i = 10;
 
  @Override
  protected Object clone() throws CloneNotSupportedException 
  {
    return super.clone();
  }
}
 
// Singleton class
class Singleton extends SuperClass
{
  // public instance initialized when loading the class
  public static Singleton instance = new Singleton();
 
  private Singleton() 
  {
    // private constructor
  }
 
  @Override
  protected Object clone() throws CloneNotSupportedException 
  {
    throw new CloneNotSupportedException();
  }
}
 
public class GFG
{
  public static void main(String[] args) throws CloneNotSupportedException 
  {
    Singleton instance1 = Singleton.instance;
    Singleton instance2 = (Singleton) instance1.clone();
    System.out.println("instance1 hashCode:- "
                         + instance1.hashCode());
    System.out.println("instance2 hashCode:- "
                         + instance2.hashCode()); 
  }
}

Output

Output:-
Exception in thread "main" java.lang.CloneNotSupportedException
	at GFG.Singleton.clone(GFG.java:29)
	at GFG.GFG.main(GFG.java:38)

If you don;t want to throw exception you can also return the same instance from clone method.

// JAVA code to explain overcome 
// cloning issue with singleton
class SuperClass implements Cloneable
{
  int i = 10;
 
  @Override
  protected Object clone() throws CloneNotSupportedException 
  {
    return super.clone();
  }
}
 
// Singleton class
class Singleton extends SuperClass
{
  // public instance initialized when loading the class
  public static Singleton instance = new Singleton();
 
  private Singleton() 
  {
    // private constructor
  }
 
  @Override
  protected Object clone() throws CloneNotSupportedException 
  {
    return instance;
  }
}
 
public class GFG
{
  public static void main(String[] args) throws CloneNotSupportedException 
  {
    Singleton instance1 = Singleton.instance;
    Singleton instance2 = (Singleton) instance1.clone();
    System.out.println("instance1 hashCode:- "
                           + instance1.hashCode());
    System.out.println("instance2 hashCode:- "
                           + instance2.hashCode()); 
  }
}

Output

Output:-
instance1 hashCode:- 366712642
instance2 hashCode:- 366712642

The Best way to implement Singleton is by using ENUM which takes care of Serialization and Other Issues on its own.