4

オブジェクト自体が生きている間に定期的に作業を行う必要があるオブジェクトがあるので、次のように設計しました。基本的に、ScheduledExecutorServiceインスタンスへの参照を含むMainクラス。この例では、すべての定期的な作業は、文字列をstdに出力することです。

コードは次のように動作することを期待しています。

  1. test2が呼び出され、メインオブジェクトo1(その中にScheduledExecutorService)が作成されます。
  2. test2レジスタを使用して、o1に毎秒1行を出力します。
  3. test2が戻り、o1がガベージになります。
  4. システムgcはgco1を起動します。これには、ローカルスケジューラをシャットダウンするfinalizeメソッドがあります。

しかし、私がこのプログラムを実行すると、何が起こるかというと、それは永遠に続くということです。基本的に、gcがo1のファイナライザーを呼び出すことはなく、その結果、スケジューラーがシャットダウンすることはなく、その結果、メインスレッドが終了しても、プログラムは終了しません。

ここで、test2()でo1.registerをコメントアウトすると、プログラムは正常に動作します。たとえば、gcが呼び出されます。また、デバッガーでは、ScheduledExecutorService.scheduleを呼び出した後にのみ実際のスレッドが作成されます。

何が起こっているのか説明はありますか?

public class Main {

public static void main(String[] args) throws Exception {
    test2();

    System.gc();
    System.out.println("Waiting for finalize to be called..");
    Thread.sleep(5000);
}

private static void test2() throws Exception {
    Main o1 = new Main();
    o1.register();
    Thread.sleep(5000);     
}

private final ScheduledExecutorService _scheduler = Executors.newSingleThreadScheduledExecutor();   

private void register() {
    _scheduler.scheduleWithFixedDelay(new Runnable() { 
        @Override public void run() { 
            System.out.println("!doing stuff...");
            }
        }, 1, 1, TimeUnit.SECONDS);
}

@Override
protected void finalize() throws Throwable  {
    try {
        System.out.print("bye");
        _scheduler.shutdown();          
    } finally {
        super.finalize();
    }       
}

}

4

2 に答える 2

7

2つの問題:

  1. デフォルトのスレッドファクトリは、デーモン以外のスレッドを作成します。メインスレッドは終了できますが、アクティブな非デーモンスレッドがある限り、JVMは終了しません。デーモンスレッドを作成するカスタムスレッドファクトリを作成する必要があると思います。
  2. 呼び出されるファイナライザーに依存しないでください。ファイナライザーが特定の時間に、またはいつでも呼び出されるという保証はありません。また、System.gc()呼び出しは、コマンドではなく、JVMへの提案として定義されます。APIドキュメントの文言は次のとおりです。

gcメソッドを呼び出すと、Java仮想マシンが未使用のオブジェクトのリサイクルに労力を費やしていることがわかります...

于 2010-08-02T01:58:55.593 に答える
3

WeakReferenceとScheduledExecutorServiceを試してみたところ、問題についての理解が深まったと思います。私のコードの中心的な問題は、次のメソッドregister()です。匿名オブジェクトRunnableを使用します。このような匿名オブジェクトの問題は、親スコープへの強力な参照が作成されることです。親スコープのフィールドを「final」にすると、Runnableのrun()メソッド内からそれらを参照できることに注意してください。run()から何も参照しない場合、このような強力な参照は作成しないと思いました。この場合に示されているように、run()で行うのは、静的な文字列を出力することだけです。ただし、観察された動作によれば、それでもそのような参照は作成されます。

private void register() {
_scheduler.scheduleWithFixedDelay(new Runnable() { 
    @Override public void run() { 
        System.out.println("!doing stuff...");
        }
    }, 1, 1, TimeUnit.SECONDS);

}

この種のプログラミングを行う正しい方法は、クラスを作成してオブジェクトを自分で渡すことです。また、弱参照のみを保持する必要があります。コードはかなり長いので、ドメインオブジェクトMainへの弱参照を保持するRunnable実装を投稿します。

private static class ResourceRefreshRunner implements Runnable
{
    WeakReference<Main> _weakRef;
    public ResourceRefreshRunner(Main o)
    {
        _weakRef = new WeakReference<Main>(o);
    }       
    @Override
    public void run() { 
        try {
            Main m = _weakRef.get();
            if (m != null) 
                m.shout(); 
            else 
                System.out.println("object not there, but future is running. ");
        } catch (Exception ex) {
            System.out.println(ex.toString());
        }
    }
}

今メインクラスでは、私は持っています:

public class Main {
ScheduledExecutorService _poolInstance;
ScheduledFuture<?> _future;
public Main(ScheduledExecutorService p)
{
    _poolInstance = p;
    _future = _poolInstance.scheduleWithFixedDelay(new ResourceRefreshRunner(this), 1, 1, TimeUnit.SECONDS);
}  ...

そしてメインのファイナライザー:

    @Override
protected void finalize() throws Throwable  {
    try {
        System.out.println("bye");
        _future.cancel(true);
    } finally {
        super.finalize();
    }       
}

この設定では、コードは期待どおりに動作します。たとえば、メインオブジェクトが参照されなくなると、GCが起動し、ファイナライザーが呼び出されます。私が行ったもう1つの実験は、_future.cancel(true)なしの実験です。finalize()で、メインオブジェクトがGC化されると、Runnable.run()の弱参照はメインオブジェクトを逆参照できなくなりますが、スレッドとタスクは引き続き実行されます。

于 2010-08-03T20:13:08.827 に答える