Internals of Java Reference Object

0

If your program need to keep a lot of objects in memory or need to perform cleanup operations on related objects before an object is reclaimed then you need to rely on reference objects. For example, you can keep track of files related to an object, and delete them when the object is reclaimed by the garbage collector. In this article, we will examine the reachability and unreachability of objects when we have a mix of ordinary and reference objects.

Reference Object

A reference object holds a reference to some other object called the referent. We pass in the referent when the reference object is created.

Reference bRef = new WeakReference<B>(new B());

The mechanics of reachability changes with the usage of a reference object. We will now see using an example how a garbage collector treats a reference object. If you want to see the same example using ordinary references, read here.

gcreftree1

In our previous article on reachable/unreachable objects, we have seen that any object reachable from the root set of references is said to be strongly reachable. But of the reference path from set root of references to the object involves at least one weak reference object, then the object is said to be weakly reachable. Weakly reachable objects, like unreachable objects, are eligible for collection.

The weak reference object for object B allows you to refer to object B but from the garbage collector’s perspective it is still unreachable from the root set.  Had it bean an ordinary reference object garbage collector would have considered it as strongly reachable. Thus weak references are treated specially by the garbage collector.

There are two reference paths to object I, one is directly from the root set and the other is from the weak reference. In such case, object I is considered as strongly reachable as it is accessible from the root set. Since object I is strongly reachable, object J is also strongly reachable.

GarbageCollectionExample:

package com.javarticles.reference;

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

public class GarbageCollectionExample {
    public static void main(String[] args) {
        List<Reference<B>> bRefList = new ArrayList<>();
        ReferenceQueue<B> queue = new ReferenceQueue<>();        
        Reference<B> bRef = new WeakReference<B>(new B(), queue);
        bRefList.add(bRef);
        Thread t = new RefRemovalthread(bRefList, queue);
        t.start();
        Reference<I> iRef = new WeakReference<I>(new I(new J()));
        B b = bRef.get();
        I i = iRef.get();
        A a = new A(bRef);
        C c = new C(bRef);
        c = null;
        F f = new F();
        f = null;
        
        System.out.println("Run GC");
        System.gc();
        System.out.println("Referent value of bRef after GC: " + bRef.get());
        
        b = null;
        
        System.out.println("Nullify strong reference to B");
        System.out.println("Run GC");
        System.gc();
        System.out.println("Referent value of bRef after GC: " + bRef.get());

        System.out.println("Referent value of iRef before GC: " + iRef.get());
        System.out.println("Keep creating objects in heap, this should throw OutOfMemoryError");
        
        boolean gcRun = false;
        StringBuilder sb = new StringBuilder();
        for (int k = 0; k < 10000000; k++) {
            try {
                sb.append("one").append(sb.toString());
            } catch (java.lang.OutOfMemoryError e) {
                System.out.println("OOM should run the gc");
                gcRun = true;
                break;
            }
        }
        if (!gcRun) {
            System.out.println("In case Gc didnt run, run now");
            Runtime.getRuntime().gc();
        } 
        System.out.println("Referent value of iRef after GC: " + iRef.get());
    }
    
    private static class RefRemovalthread extends Thread {
        private List<Reference<B>> refList;
        private ReferenceQueue<B> queue;
        RefRemovalthread(List<Reference<B>> refList,
                ReferenceQueue<B> queue) {
            super("Ref Removal Thread");
            setPriority(Thread.MAX_PRIORITY);
            setDaemon(true);
            this.refList = refList;
            this.queue = queue;
        }

        @Override
        public void run() {     
            System.out.println("Start RefRemoval Thread");
            while (refList.size() > 0) {
                try {
                    System.out.println("Try removing the ref");
                    final Reference<B> bRef = (Reference<B>) queue.remove();
                    refList.remove(bRef);
                    System.out.println("Removed queued reference to B");
                } catch (final InterruptedException e) {
                    continue;
                }
            }
        }
    }

}

A:

package com.javarticles.reference;

import java.lang.ref.Reference;

public class A {
    private Reference<B> bRef;
    
    public A(Reference<B> bRef) {
        this.bRef = bRef;
    }
    
    @Override
    public void finalize() {
        System.out.println("A cleaned");
    }
}

B:

package com.javarticles.reference;

public class B {
    private E e = new E();

    @Override
    public void finalize() {
        System.out.println("B cleaned");
    }
}

C:

package com.javarticles.reference;

import java.lang.ref.Reference;

public class C {
    private Reference<B> bRef;
    private D d = new D();

    public C(Reference<B>  bRef) {
        this.bRef = bRef;
    }

    @Override
    public void finalize() {
        System.out.println("C cleaned");
    }
}

D:

package com.javarticles.reference;

public class D {
    @Override
    public void finalize() {
        System.out.println("D cleaned");
    }
}

E:

package com.javarticles.reference;

public class E {
    @Override
    public void finalize() {
        System.out.println("E cleaned");
    }
}

F:

package com.javarticles.reference;

//F->G->H->F
public class F {
    private G g = new G(this);
    @Override
    public void finalize() {
        System.out.println("F cleaned");
    }
}

G:

package com.javarticles.reference;

public class G {
    private H h;
    public G(F f) {
        h = new H(f);
    }
    @Override
    public void finalize() {
        System.out.println("G cleaned");
    }
}

H:

package com.javarticles.reference;

public class H {
    private F f;

    public H(F f) {
        this.f = f;
    }
    @Override
    public void finalize() {
        System.out.println("H cleaned");
    }
}

I:

package com.javarticles.reference;

public class I {
    private J j;

    public I(J j) {
        this.j = j;
    }
    @Override
    public void finalize() {
        System.out.println("I cleaned");
    }
}

J:

package com.javarticles.reference;

public class J {

    @Override
    public void finalize() {
        System.out.println("J cleaned");
    }
}

Once the garbage collector claims a weakly reachable object, all weak references to it are set to null so the object can no longer be accessed through the weak reference.

Output:

Start RefRemoval Thread
Try removing the ref
Run GC
Referent value of bRef after GC: [email protected]
Nullify strong reference to B
Run GC
G cleaned
Referent value of bRef after GC: null
B cleaned
Removed queued reference to B
E cleaned
Referent value of iRef before GC: [email protected]
C cleaned
Keep creating objects in heap, this should throw OutOfMemoryError
F cleaned
D cleaned
H cleaned
OOM should run the gc
Referent value of iRef after GC: [email protected]

Type of Reference Objects

There are three types of references:

  1. Soft reference – Garbage collector may or may not reclaim a softly reachable object but is certain to reclaim before throwing an OutOfMemoryError
  2. Weak reference – Garbage collector always reclaims it.
  3. Phantom reference – Once an object is reclaimed, you may want to release the related resources. Instead of relying on the finalization mechanism, you may want to create a phantom reference that refers to the object. As soon as the garbage collector determines that its referent may be reclaimed, it enqueues the phantom reference.  One can have a separate thread trying to remove the referent from the queue, once the referent is removed, the post-reclaim cleanup actions related to the referent can be performed. Phantom reference’s get() always returns null to make sure the object remains reclaimable. Also it must always be created with a reference queue so that we know when an object is eligible for reclaim. Unlike soft and weak references, garbage collector doesn’t clear the referent automatically and the program has to explicitly call clear().

What is a Reference Queue?

Once the garbage collector determines that an object is not directly reachable and is reachable only through reference objects, it adds the reference to the pending reference list. There is a separate reference handler thread running that enqueues the pending reference to the registered queue.

States of Reference

A reference object is in one of the below states:

Reference States

Reference States

Below describes each state:

  1. Active – Some time after the collector detects that the referent is no more reachable, it changes the state to Pending.
    Active Reference

    Active Reference

  2. Pending – The next reference points to itself.
    Pending Reference

    Pending Reference

    After GC adds the instance to the pending-Reference list, the reference handler thread removes the pending reference adds it to the queue. Next pending reference is linked thru the ‘discovered’ attribute.Pending List

  3. Enqueued – Enqueued reference points to special reference queue ‘ENQUEUED’, the next points to the next element in queue, or to itself if at end of list.
    Reference Enqueued

    Reference Enqueued

    When an instance is removed from its ReferenceQueue, it is made Inactive.

  4. Inactive – Pending reference points to NULL ReferenceQueue and next reference points to NULL.
    Inactive Reference

    Inactive Reference

Download the source code

This was an example about the internals of java reference.

You can download the source code here: javareferenceobjectexample.zip

About Author

Ram's expertise lies in test driven development and re-factoring. He is passionate about open source technologies and loves blogging on various java and open-source technologies like spring. You can reach him at [email protected]

Comments are closed.