"We have a bit of a dead code problem," writes R.S., "most of the time, it's different versions (sometimes older, sometimes newer) of the same class that were created as part of a good-intentioned refactoring that was never quite completed."
"And then we have mysterious classes like these."
package com.initech.test;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.log4j.Logger;
/**
* 0 475,734
* <p>
* 1 475,828
* <p>
* 10 476,250
* <p>
* 100 480,469
* <p>
* 1,000 522,656
* <p>
* 10,000 953,359
* <p>
* 100,000 5,073,000
* <p>
* 1,000,000 46,406,000
* <p>
* 10,000,000 458,456,000
* <p>
* ~45 bytes per rubbish count
*
* @author
*/
public final class RubbishGenerator
implements RubbishGeneratorMBean, Runnable
{
private final static Logger LOG = Logger.getLogger(RubbishGenerator.class);
private final AtomicInteger workerCount = new AtomicInteger(1);
private final AtomicInteger rubbishPerLoop = new AtomicInteger(10000000);
private final AtomicInteger loopDelayMs = new AtomicInteger(1000);
private final AtomicBoolean enabled = new AtomicBoolean(false);
private final AtomicInteger changeSerialNo = new AtomicInteger(0);
private final AtomicLong loopCount = new AtomicLong(0);
private ExecutorService workerExecutor;
private ExecutorService controllerExecutor;
public RubbishGenerator ()
{
}
public static void main (final String[] args)
{
RubbishGenerator g = new RubbishGenerator();
synchronized (g)
{
try
{
final Object o = g.generateRubbish();
System.out.println("Rubbish creation completed");
System.gc();
g.wait(1000000);
// so its not optimized away
System.out.println(o);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
/*
* (non-Javadoc)
*
* @see com.initech.test.RubbishGeneratorMBean#getRubbishPerLoop()
*/
@Override
public int getRubbishPerLoop ()
{
return rubbishPerLoop.get();
}
/*
* (non-Javadoc)
*
* @see com.initech.test.RubbishGeneratorMBean#getLoopDelayMs()
*/
@Override
public int getLoopDelayMs ()
{
return loopDelayMs.get();
}
/*
* (non-Javadoc)
*
* @see com.initech.test.RubbishGeneratorMBean#getThreadCount()
*/
@Override
public int getWorkerCount ()
{
return workerCount.get();
}
/*
* (non-Javadoc)
*
* @see com.initech.test.RubbishGeneratorMBean#setRubbishPerLoop(int)
*/
@Override
public void setRubbishPerLoop (final int rubbishPerLoop)
{
synchronized (this)
{
if (rubbishPerLoop == this.rubbishPerLoop.get())
{
return;
}
if (rubbishPerLoop < 1)
{
throw new IllegalArgumentException("byteCount must be > 0, was: " + rubbishPerLoop);
}
this.rubbishPerLoop.set(rubbishPerLoop);
changeSerialNo.incrementAndGet();
this.notifyAll();
}
}
/*
* (non-Javadoc)
*
* @see com.initech.test.RubbishGeneratorMBean#setLoopDelayMs(int)
*/
@Override
public void setLoopDelayMs (final int delayMs)
{
synchronized (this)
{
if (delayMs == loopDelayMs.get())
{
return;
}
if (delayMs < 1)
{
throw new IllegalArgumentException("delayMs must be > 0, was: " + delayMs);
}
loopDelayMs.set(delayMs);
changeSerialNo.incrementAndGet();
this.notifyAll();
}
}
/*
* (non-Javadoc)
*
* @see com.initech.test.RubbishGeneratorMBean#setThreadCount(byte)
*/
@Override
public void setWorkerCount (final int workerCount)
{
synchronized (this)
{
if (workerCount == this.workerCount.get())
{
return;
}
if (workerCount < 1)
{
throw new IllegalArgumentException("workerCount must be > 0, was: " + workerCount);
}
this.workerCount.set(workerCount);
changeSerialNo.incrementAndGet();
this.notifyAll();
}
}
/*
* (non-Javadoc)
*
* @see com.initech.test.RubbishGeneratorMBean#getEnabled()
*/
@Override
public boolean getEnabled ()
{
return enabled.get();
}
/*
* (non-Javadoc)
*
* @see com.initech.test.RubbishGeneratorMBean#getLoopCount()
*/
@Override
public long getLoopCount ()
{
return loopCount.get();
}
/*
* (non-Javadoc)
*
* @see com.initech.test.RubbishGeneratorMBean#setEnabled(boolean)
*/
@Override
public void setEnabled (final boolean enabled)
{
synchronized (this)
{
if (enabled == this.enabled.get())
{
return;
}
this.enabled.set(enabled);
//changeSerialNo.incrementAndGet();
// a notifyAll wouldn't matter here since we are synchronized and
// there aren't any executor services or we are going to shutdown all the
// executor services immediately.
// this.notifyAll();
if (!enabled)
{
shutdown();
}
else
{
startup();
}
}
}
private void startup ()
{
LOG.info("Starting up");
synchronized (this)
{
if (controllerExecutor != null)
{
throw new IllegalStateException("Logic error: Startup should not be called while a controllerExecutor exists");
}
controllerExecutor = Executors.newSingleThreadExecutor();
controllerExecutor.execute(this);
}
}
public void shutdown ()
{
LOG.info("Shutting down");
synchronized (this)
{
if (workerExecutor != null && !workerExecutor.isShutdown())
{
workerExecutor.shutdownNow();
}
if (controllerExecutor != null && !controllerExecutor.isShutdown())
{
controllerExecutor.shutdownNow();
}
workerExecutor = null;
controllerExecutor = null;
}
}
/**
* Controller reconfigures rubbish collector on changes.
*/
@Override
public void run ()
{
synchronized (this)
{
while (true)
{
if (workerExecutor != null)
{
throw new IllegalStateException("Logic error workerExecutor was not null");
}
LOG.info("Starting new workers");
LOG.info(String.format("workerCount=%d, rubbishCountPerLoop=%d, loopDelayMs=%d", workerCount.get(), rubbishPerLoop.get(), loopDelayMs.get()));
workerExecutor = Executors.newFixedThreadPool(workerCount.get());
for (int i = workerCount.get(); i > 0; i--)
{
workerExecutor.execute(new Worker());
}
waitForDisableOrConfigChange();
if (!enabled.get())
{
LOG.info("Disabled, shutting down controller and workers");
shutdown();
return;
}
LOG.info("Config changed detected shutting down old workers");
if (workerExecutor != null && !workerExecutor.isShutdown())
{
workerExecutor.shutdownNow();
}
workerExecutor = null;
}
}
}
/**
* Must be called from block synchronized on RubbishGenerator instance.
*/
private void waitForDisableOrConfigChange ()
{
final int serialNo = changeSerialNo.get();
while (enabled.get() && serialNo == changeSerialNo.get())
{
try
{
this.wait();
}
catch (InterruptedException e)
{
LOG.info("Controller interrupted, shutting workers and controller down", e);
shutdown();
return;
}
}
}
/**
* Generates rubbish
*/
class Worker
implements Runnable
{
final int serialNo = RubbishGenerator.this.changeSerialNo.get();
Object rubbishRef;
@Override
public void run ()
{
// if the serialNo changes, exit
while (serialNo == changeSerialNo.get())
{
// null rubbish so don't exhaust memory with large rubbish
rubbishRef = null;
try
{
rubbishRef = generateRubbish();
loopCount.incrementAndGet();
}
catch (final Exception e)
{
final String mesg = "Error generating rubbish, shutting worker down";
LOG.error(mesg, e);
throw new RuntimeException(mesg, e);
}
synchronized (RubbishGenerator.this)
{
if (!enabled.get() || serialNo != changeSerialNo.get()) {
break;
}
try
{
RubbishGenerator.this.wait(loopDelayMs.get());
}
catch (final Exception e)
{
LOG.info("Exception, exiting", e);
return;
}
}
}
LOG.info("Configuration changes occurred, shutting down");
}
}
/**
* For each count of rubbish should consume about 40 bytes, 24 for LinkedList
* Entry and 16 for Rubbish instance.
* <p>
* Each entry consumes 24 bytes, 8 bytes for header, 4 bytes for prev, next,
* and object, and 4 bytes to pad up to multiple of 8 bytes.
*
* @return
*/
private Object generateRubbish ()
{
final LinkedList<Rubbish> rubbish = new LinkedList<Rubbish>();
Rubbish prev = new Rubbish(null);
Rubbish cur = new Rubbish(prev);
prev.next = cur;
for (int i = rubbishPerLoop.get(); i > 0; i--)
{
rubbish.add(prev);
prev = cur;
cur = new Rubbish(prev);
prev.next = cur;
}
return rubbish;
}
/**
* 8 byte header + 4 bytes for each pointer = 16 bytes
* <p>
* The extra linking is to make the rubbish collector have to do some work.
*/
class Rubbish
{
Rubbish prev;
Rubbish next;
Rubbish (final Rubbish prev)
{
this.prev = prev;
}
}
}