在收集子类的最后一个实例时是否会收集抽象超类垃圾?

时间:2018-10-18 03:32:16

标签: java static garbage-collection superclass timertask

我有一个抽象类,用于使子类的实例到期:

public abstract class Expirable {
    private transient Timer timer;

    protected abstract void onExpire();

    protected void setExpire(long delay) {
        resetExpire();

        timer = new Timer();
        timer.schedule(new TimerTask() {
                           @Override
                           public void run() {
                               resetExpire();
                               onExpire();
                           }
                       }, delay
        );
    }

    protected void resetExpire() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }
}

我扩展任何类并覆盖onExpire,然后从子类(未显示)调用setExpire(delay)

public class MyClass extends Expirable {
    @Override
    protected void onExpire() {
        // expiration code here
    }
}

该类工作得很好,但是timer对象非常昂贵。因为我有成千上万的实例,所以我编写了一个具有相同功能的廉价版本,该版本使用以固定速率安排的单个timerqueue。不够精确,但是便宜。

public abstract class ExpirableCheap {
    private static final long COLLECTOR_INTERVAL_MSEC = 1000;
    private static final int INITIAL_CAPACITY = 128;

    private static Queue<ExpirableCheap> queue = new PriorityBlockingQueue<>(
            INITIAL_CAPACITY,
            Comparator.comparingLong(expirable -> expirable.expiresWhen)
    );

    @SuppressWarnings("FieldCanBeLocal")
    private static TimerTask timerTask;
    @SuppressWarnings("FieldCanBeLocal")
    private static Timer timer;
    static {
        timerTask = new TimerTask() {
            @Override
            public void run() {
                // this Runnable stops working
                long time = new Date().getTime();
                while (queue.peek() != null && queue.peek().expiresWhen < time) {
                    queue.poll().onExpire();
                }
            }
        };
        timer = new Timer();
        timer.scheduleAtFixedRate(timerTask,
                COLLECTOR_INTERVAL_MSEC,
                COLLECTOR_INTERVAL_MSEC
        );
    }

    private transient long expiresWhen;

    protected abstract void onExpire();

    protected synchronized void setExpire(long delay) {
        resetExpire();

        long time = new Date().getTime();
        expiresWhen = time + delay;

        queue.offer(this);
    }

    protected synchronized void resetExpire() {
        queue.remove(this);
    }
}

很明显,静态代码块执行一次,并以固定间隔调度timertimerTask窥视队列并呼叫onExpire()

怎么了?

这可以运行一会儿,但是突然timerTask不再执行。在测试时,它工作正常,我无法模拟这种情况,但是在生产一段时间后失败了。

我不确定会发生什么,但是我怀疑在收集子类的最后一个实例时,我在静态代码块中初始化的静态变量是垃圾回收。然后,当重新使用该类时,将不再运行静态代码块。换句话说,它似乎可以工作,直到extend ExpirableCheap不再存在实例为止。

奇怪的是,queue仍然存在,这是为什么我希望Runnable内部发生异常的原因,我认为事实并非如此。

如您所见,我尝试将timertimerTask变量从静态代码块移到成员变量中(这没有帮助)。我还尝试同步setExpire()resetExpire(),我认为这也没有什么区别。

有人可以看到发生了什么吗?我犯了另一个愚蠢的错误,我走错了路吗?

有什么建议我可以做些什么来使它起作用?

1 个答案:

答案 0 :(得分:0)

正如@TimBiegeleisen正确指出的那样,Java可以按预期工作。当子类的最后一个实例被收集时,抽象超类不会被垃圾收集。

我的问题无关。