1. Strong Reference
Strong reference is the most common kind of reference. We use this kind of reference every day.
For example:
Foo firstReference = new Foo();
Foo secondReference = firstReference;
firstReference = null;
secondReference = null;
//Now the GC can collect an instance of the Foo, which created in the first line.
The created instance of the Foo class is not available for the garbage collector while there is at least one link to this object.
2. WeakReference
WeakReference
- this is the type of references
that will be removed by the garbage collector on the next pass,
if there are no other type references to the object.
You can get an object value from the WeakReference
until the GC decides to collect the object.
As soon as the GC decides to do it (not after the GC finalize the object and clear an allocated memory),
you will get the NULL from the WeakReference
.
This happens when the GC is just marking the object for a further processing.
It is important to understand, that all finalization actions are executed only after this.
When we look at the PhantomReference
, we’ll return to this point.
Let’s try to testing WeakReference
behavior:
@Test
public void testWeakAfterGC() {
// Arrange
String instance = new String("123");
WeakReference<String> reference = new WeakReference<>(instance);
// Act
instance = null;
System.gc();
// Asserts
Assertions.assertThat(reference.get()).isNull();
}
This test was successful.
Java provides us the WeakHashMap
data structure.
It’s a something like a HashMap
,
which uses the WeakReference
as a key of the Map.
If a key of the WeakHashMap
becomes garbage, its entry is removed automatically.
How we can test a WeakHashMap
behavior:
@Test
public void testWeakMap() throws InterruptedException {
// Arrange
WeakHashMap<String, Boolean> map = new WeakHashMap<>();
String instance = new String("123");
map.put(instance, true);
// Act
instance = null;
GcUtils.fullFinalization();
// Asserts
Assertions.assertThat(map).isEmpty();
}
Results of weak tests:
You might think that the WeakHashMap is a good solution for creating a cache,
but you should understand what kind of behavior you need.
Because if we are talking about a cache that removes data from a storage
only when we reached a memory limit, then you should looking at the using of SoftReferences .
|
3. SoftReference
The behavior of SoftReference
is similar to WeakReference
,
but GC collect this kind of reference only when
our application does not have enough of memory.
Let’s try to test it:
@Test
public void softTest() {
// Arrange
String instance = new String("123323");
SoftReference<String> softReference = new SoftReference<>(instance);
instance = null;
Assertions.assertThat(softReference).isNotNull();
Assertions.assertThat(softReference.get()).isNotNull();
// Act
GcUtils.tryToAllocateAllAvailableMemory(); (1)
// Asserts
Assertions.assertThat(softReference.get()).isNull();
}
1 | at this line we try to get all available memory until is the OutOfMemoryError thrown.
I used GcUtils , this describes below. |
The GC collects our SoftReference
before we get the OutOfMemoryError
.
This behavior is a good reason to use SoftReferences
as a cache for a
data that is difficult to build in memory.
For example, a reading video or graphics data from a slow file storage. When your application has enough of memory, you can receive this data from the cache, but if application reaches of a memory limit, then the cache cleans. And now, you need restore this data on the next request.
However in many cases you need to prefer a cache based on the LRU algorithm.
4. PhantomReference
The PhantomReferences
are enqueued only when the object is physically removed from memory.
The get()
method of the PhantomReference always returns NULL
,
specially to prevent you from being able to resurrect an almost removed object.
The PhantomReference
provides you an ability to determine exactly when an object was removed from memory.
In order for implementation this we need to work with a ReferenceQueue
.
When the referent object of a PhantomReference
, removes from a memory
then the GC enqueues the phantomReference in the referenceQueue
and we can poll this reference from this queue.
Let’s look at the code:
@Test
public void testQueuePollAfterFinalizationGC() {
// Arrange
Foo foo = new Foo();
ReferenceQueue<Foo> referenceQueue = new ReferenceQueue<>();
PhantomReference<Foo> phantomReference = new PhantomReference<>(foo, referenceQueue);
// Act
foo = null;
GcUtils.fullFinalization();
// Asserts
Assertions.assertThat(phantomReference.isEnqueued()).isTrue();
Assertions.assertThat(referenceQueue.poll()).isEqualTo(phantomReference);
}
private class Foo {
@Override
protected void finalize() throws Throwable {
System.out.println("FIN!");
super.finalize();
}
}
I used my utility class (GcUtils.java), to test the behavior of phantom references. This tools are ensure that the GC accurately performs a full cycle of cleaning and finalization. You can find source code of this utility on my github: github.com/antkorwin/common-utils |
For example, if you need to allocate memory for processing of a large video file,
only after finish processing of a previous file and release memory
(that was allocated in the previous step) then using a PhantomReference
is a right decision.
Because you can request a new part of memory exactly after the GC is release a previous used part.
So, this reduces a chance to get the OutOfMemoryError
when you already released a part of memory
but the GC has not collected it yet.
Finalization may does not happened in a timely fashion,
it is difficult to predict the number of garbage collection cycles
while the object was waiting for finalization.
It can lead to serious delay in actually cleaning up garbage objects,
and become the reason of you can get an OutOfMemoryErrors
even when most of the heap is garbage.
I wrote a simple utility to checking a code on memory leaks, which is using phantom references:
Example of using this tool:
@Test
public void testWithoutLeaks() {
// Arrange
Foo foo = new Foo();
LeakDetector leakDetector = new LeakDetector(foo);
// Act
foo = null;
// Asserts
leakDetector.assertMemoryLeaksNotExist();
}
@Test
public void testWithLeak() {
// Arrange
Foo foo = new Foo();
Foo bar = foo;
LeakDetector leakDetector = new LeakDetector(foo);
// Act
foo = null;
// Asserts
leakDetector.assertMemoryLeaksExist();
}
Let’s look at the code of the memory leaks detector:
public class LeakDetector extends PhantomReference<Object> {
private final String description;
/**
* Initialization of the memory leaks detector.
* @param referent the object(resource) for which we are looking for leaks.
*/
public LeakDetector(Object referent) {
super(referent, new ReferenceQueue<>());
this.description = String.valueOf(referent);
}
/**
* If exists memory leaks then throws a fail.
*
* WARN: Run this method after delete all references on the checkable object(resource)
*/
public void assertMemoryLeaksNotExist() {
GcUtils.fullFinalization();
Assertions.assertThat(isEnqueued())
.as("Object: " + description + " was leaked")
.isTrue();
}
/**
* If not exists memory leaks then throws a fail.
*
* WARN: Run this method after delete all references on the checkable object(resource)
*/
public void assertMemoryLeaksExist() {
GcUtils.fullFinalization();
Assertions.assertThat(isEnqueued())
.as("Object: " + description + " already collected by the GC")
.isFalse();
}
}