How to ensure Objects in Object[] array are always eligible for GC
Given example code such as:
publicclass Consumer
{
Object[] oArray =new Object[4];
private Object[] arrayify(Object o1, Object o2, Object o3, Object o4)
{
oArray[0] = o1;
oArray[1] = o2;
oArray[2] = o3;
oArray[3] = o4;
}
publicvoid consume(Object o1, Object o2, Object o3, Object o4)
{
doStuff(arrayify(o1, o2, o3, o4));
}
/*
... other code follows
*/
}
Now I'd like to ensure that the Object array does not maintain a hold on the objects so that the GC can collect them if nothing else is referencing them (a single Consumer class is active during the lifetime of the application). As far I can tell, I have two options:
- I can make it an array of WeakReferences, but this will not work with legacy code.
- I can null out the array when I'm finished with it - but this will be called millions of times and that can be a performance issue.
Does anyone have any suggestions for improvement?
[1423 byte] By [
cvweiss__a] at [2007-11-27 7:50:56]

Is that a big deal? It depends, of course, on how large the four objects are, including objects they refer to directly and indirectly.
Remember that next time consume() is called, the object references in the array are overwritten so the old objects become eligible for GC. If consume gets called a million times, that might be soon enough?
If not, putting four nulls into the array may be the solution. I can't think it will have a hard time penalty. It certainly does not take longer than the arrayify procedure did in the first place, and only a fraction of the time it will take for the garbage collector to collect the objects afterwards.
Please, I'm not trying to ridicule your question at all. I just don't know your setup, which I might need to do in order to judge about solutions for your question.
OleVVa at 2007-7-12 19:32:01 >

My tired old laptop can clear four hundred million Object[4]'s per second. Test program below, remember to use "java -server" as always when performance is an issue.
public class t
{
public static void main(String args[])
{
System.out.println("Ignore the first few timings.");
System.out.println("They may include Hotspot compilation time.");
System.out.println("I hope you are running me with \"java -server\"!");
for (int n = 0; n < 5; n++)
doit();
System.out.println("Did you run me with \"java -server\"? You should have.");
System.out.println("Forgetting \"-server\" makes baby Cthulhu cry.");
}
static Object arr[] = new Object[4];
static void arrayify(Object a, Object b, Object c, Object d)
{
arr[0] = a;
arr[1] = b;
arr[2] = c;
arr[3] = d;
}
public static void doit()
{
long start = System.currentTimeMillis();
for (int n = 0; n < 400 * 1000 * 1000; n++) {
arrayify(null, null, null, null);
}
long end = System.currentTimeMillis();
System.out.println("time " + (end - start) + " ms");
}
}
> Is that a big deal? It depends, of course, on how
> large the four objects are, including objects they
> refer to directly and indirectly
> ...
You're not ridiculing at all - so no worries there!
These objects, overall, can be very large - and we're trying to keep some memory constraints here.
I was thinking about it during lunch, and I came to the same conclusion that you did. Setting them to null really won't be a big deal at all.
Well, frankly, I'm not sure where you're going with this class... Because let's say you have a Consumer instance that you keep. That's fine. But if you are only calling consume() with 4 objects, then why bother holding the array in Consumer directly.
If you want, after consume() for those objects to be not held, then don't use the oArray field, just do this:
public class Consumer
{
private Object[] arrayify(Object o1, Object o2, Object o3, Object o4)
{
Object[] oArray = new Object[4];
oArray[0] = o1;
oArray[1] = o2;
oArray[2] = o3;
oArray[3] = o4;
return oArray;
}
public void consume(Object o1, Object o2, Object o3, Object o4)
{
doStuff(arrayify(o1, o2, o3, o4));
}
/*
... other code follows
*/
}
Then nothing is held (except maybe whatever held the 4 objects passed to consume().
If another method needs the array within that class, that's one thing, but you are passing the array directly to doStuff(), and it seems a better design to continue to pass the object array around to other methods that need it.
Otherwise hold oArray as an instance field like you do and don't pass it to doStuff, let it use the oArray directly that you set in arrayify(), then set it all to null when done with it.
Message was edited by:
bsampieri
> If you want, after consume() for those objects to be
> not held, then don't use the oArray field, just do
> this:
Believe me, this is exactly how I preferred to do it. Unfortunately, this method creates an Object[] array for every call to the method. Memory is already tight, and we have to keep the GC time to a minimum.Yes, I understand that the millions of Object[] arrays are immediately eligible for collection, but Java's GC on Tru64 Unix will stop the world from spinning just so it can garbage collect - this can take anywhere from 5 to 30 seconds where absolutely nothing is running in the VM except the GC thread. Unacceptable.
So I have the code you see in the OP, and as discussed the code has been modified to just null out the array.
> Believe me, this is exactly how I preferred to do it.
> Unfortunately, this method creates an Object[] array
> for every call to the method. Memory is
> already tight, and we have to keep the GC time to a
> minimum.Yes, I understand that the millions of
> Object[] arrays are immediately eligible for
> collection, but Java's GC on Tru64 Unix will stop
> the world from spinning just so it can garbage
> collect - this can take anywhere from 5 to 30
> seconds where absolutely nothing is running in the
> VM except the GC thread. Unacceptable.
So you've profiled it to see that it is actually a problem? Maybe you need to try a different GC strategy (yes, there's a way to change it, I forget how).
> So I have the code you see in the OP, and as
> discussed the code has been modified to just null out
> the array.
That's fine... but I assume then you don't have multiple threads accessing the same Consumer instance, which could cause the state of your object array to become changed between internal method calls.
> So you've profiled it to see that it is actually a
> problem? Maybe you need to try a different GC
> strategy (yes, there's a way to change it, I forget
> how).
Yup, I've tried the alternatives and profiled each one. I'm user the lesser of the evils.
> That's fine... but I assume then you don't have
> multiple threads accessing the same Consumer
> instance, which could cause the state of your object
> array to become changed between internal method calls.
Just the one thread for now - multiple threads with synchronization are on the Todo list - but that's easy. I'm still tackling a couple of tough issues and I prefer working with a single thread when debugging (whenever possible).
if the objects in the array are large then you have a couple of issues.
a) the objects are reused for many calls to the consume method. In which case reusing the array will reduce GC (but cause threading issues) but probably not by a significant margin.
b) the objects are not reused. In which case GC'ing them will dwarf the cost of GC'ing the array on each call. (they have to be created and GC'd on each call to the array.
Some tips regarding long GC times.
a) if you are caching objects for reuse it can dramatically impact GC times. objects created in new (eden) generations can be trivially cleared up if it goes out of scope rapidly (like most objects do) as you can just copy live objects to the next generation (in the mean while other new objects are being created in the second bank of the new generation, i.e it is mostly asynchronous). Objects in later generations are signficantly more expensive to clean up as you have to compact the memory region continaing that generation. This involves tracing all live and dead objects, removing the dead ones and moving the live ones (stop the world, although some vm's can perform some of this in the background). Keeping objects around for a long time can cause the later generations to become full causing expensive and mostly useless GC's to occur.
b) profile it using the GC monitoring tools (they tell you which generations are taking the most time to GC). i.e if it takes 5 seconds to GC a perm generation and only frees up a few kilobytes of memory then you need to either increase the size of the perm gen or stop holding objects for the amount of time it takes to cause thm to be moved into that generation.
c) profile it some more (the above was one example of a problem there are many others that can cause similar issues)
matfud
