How to Test Code in the Garbage Collector
Want to learn more about using phantom references in Java? Take a look at this post where we explore how to test code in the garbage collector.
Join the DZone community and get the full member experience.
Join For Freestrong reference
the 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.
weakreference
weakreference
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 test the
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.
also, java provides us with the
weakhashmap
data structure. it’s 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.
let's take a look at 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();
}
these are the results of the weak tests:
you might think that the
weakhashmap
is a good solution for creating a cache, but you should understand the 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 look at using the
softreferences
.
softreference
the behavior of
softreference
is similar to
weakreference
, but the 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(); //at this line we try
// to get all available memory until is the outofmemoryerror thrown.
// i used gcutils, this describes below.
// asserts
assertions.assertthat(softreference.get()).isnull();
}
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 to restore this data on the next request.
however, in many cases, you need to prefer a cache based on the lru algorithm.
phantomreference
the
phantomreferences
are enqueued only when the object is physically removed from memory.
the
get()
method of the
phantomreference
always returns
null
, especially to prevent you from being able to resurrect an almost removed object.
the
phantomreference
provides you with the ability to determine exactly when an object was removed from memory. in order for implementation, we need to work with a
referencequeue
. when the referent object of a
phantomreference
is removed 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. these tools ensure that the gc accurately performs a full cycle of cleaning and finalization. you can find the source code of this utility on github .
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 the right decision. because you can request a new part of memory exactly after the gc is released as a previously used part. so, this reduces the chance to get the
outofmemoryerror
when you already released a part of memory. but, the gc has not collected it yet.
finalization may 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 a serious delay in actually cleaning up garbage objects and become the reason you can get an
outofmemoryerrors,
even when most of the heap is garbage.
i wrote a simple utility to check a code on memory leaks, which is using phantom references.
here is an 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 take a 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();
}
}
references
the source code of this project is available on github .
Published at DZone with permission of Anatoliy Korovin. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments