@blog.justoneplanet.info

日々勉強

GCとメモリ

  • Java VMは参照されているオブジェクトがメモリに存在することを保証する
  • Java VMは参照されなくなったオブジェクトをGCによって回収しメモリ領域を解放する

■GC

オブジェクトはnewを使用して生成されるが、freeやdeallocなど削除を明示しない。

  • 他のオブジェクトを参照するように変更する
  • nullを参照するように変更する
  • メソッドが戻るなどして参照がなくなる

上述のタイミングでGCはオブジェクトを回収する。

GCの実装について

参照カウンタ方式

循環参照に対して機能しない事から殆どのGCの実装は使用していない。

マークアンドスイープ
  1. 直接到達可能なオブジェクトの集合rootを決定する(ローカル変数から参照されているオブジェクトなど)
  2. rootから参照可能なオブジェクトを到達可能とマークする
  3. マークされていない到達可能なオブジェクトがなくなるまで続く
  4. マークされていないオブジェクトを回収する

基本的にはマーク中にプログラムの実行を停止する必要がある。また、仮想マシンの実装により複数のコレクションアルゴリズムから選択される。

注意

  • 新たなオブジェクトのために常にメモリがあることを保証しない(OutOfMemory)
  • 必要のない参照を残すことによってメモリリークを起こす可能性がある

意識しなくて良い事と存在しない事は別の話である。

■ファイナライズ

finalize記述するする必要がないようにするべきである。

protected void finalize() throws Throwable
GCが回収するべきと判断されたときに実行される。どのスレッドで実行されるか分からず実行自体も何ら保証してはいない

明示して記述する場合

記述する場合は以下のように注意する必要がある。

public class ProcessFile {
    private FileReader file;
    public ProcessFile(String path) throws FileNotFoundException {
        file = new FileReader(path);
    }
    public synchronized void close() throws IOException {
        if (file != null) {
            file.close();
            file = null;
        }
    }
    protected void finalize() throws Throwable {
        try {
            close();
        }
        finally {
            super.finalize();// 忘れないように呼び出す!
        }
    }
}

finalizeはコールされることがない可能性を考慮しfinalizeによってコールされるcloseは2回以上コールされても動作する必要がある。finalizeはプログラマーがリソースの解放を忘れた時の安全対策にしかならないと考える必要がある。

■GCを呼び出す

public void gc()
オブジェクトの回収を仮想マシンに要求する
public void runFinalization()
到達不可能なオブジェクトでfinalizeが実行されていないオブジェクトのfinalizeの実行を要求する
public long freeMemory()
システムメモリの空きバイト数を返す
public long totalMemory()
システムメモリ内の全バイト数を返す
public long maxMemory()
仮想マシンが使用するメモリの最大バイト数を返す

以下のようにして

System.gc();
Runtime.getRuntime().gc();

GCを明示して呼び出すことには以下のようなメリットがある。

  • できるだけメモリがフリーの状態で実行できる
  • 処理中にGCが動作する可能性を低減できる

■参照オブジェクト

public T get()
参照オブジェクトのリファレントオブジェクトを返す
public void clear()
参照オブジェクトをクリアして、リファレントオブジェクトを持たないようにする
public boolean enqueue
参照オブジェクトが登録されている参照キューがあれば、キューに参照オブジェクトを追加する。入れられたらtrueを返し、登録されたキューが存在しないか既にキューに入れられていたらfalseを返す
public boolean isEnqueued
参照オブジェクトがキューに入れられていたらtrueを返す

提供されている参照オブジェクトの種類は以下のとおりである。

  • SoftRefernce<T>
  • WeekRefernce<T>
  • PhantomRefernce<T>

用例

以下のようにしてWeakReferenceを使用することができる。

class DataHandler {
    private File lastFile;
    //private WeakReference<File> lastFile;
    private WeakReference<byte[]> lastData;// フィールドで保持
    byte[] readFile(File file) {
        byte[] data;
        if (file.equals(lastFile)) {// ...(a)最後に開いたファイルと同じの場合
            data = lastData.get();
            if (data != null) {// ...(b)最後に返されてからデータがGCされていないばあい
                return data;
            }
        }
        // (a)ファイルを開いていない場合
        // (b)最後に返されてからGCされている場合
        // 新しくファイルを読み込んでlastFileとlastDataに保持する
        data = readBytesFromFile(file);
        lastFile = file;
        //lastFile = new WeakReference<File>(file);
        lastData = new WeakReference<byte[]>(data);
        return data;
    }
}

仮に強い参照でdataを保持した場合、DataHandlerオブジェクトが到達可能な限りdataは回収されない。WeakReferenceを使用することで時々データをディスクから読み出す。IOのコストは増加するがメモリの占有率は低下させることができる。

■参照キュー

Referenceクラスのサブクラスは次の形式のコンストラクタを提供する。

public SoftReference(T referent, ReferenceQueue q)
public WeakReference(T referent, ReferenceQueue q)
public PhantomReference(T referent, ReferenceQueue q)
指定されてたリファレントで新たな参照オブジェクトを作成し、指定されたQueueに登録する

ReferenceQueueクラスはキューから参照を取り除くため、以下の3つのメソッドを提供している。

public Reference poll()
キューから次の参照オブジェクトを取り除いて返す。空ならばnullを返す
public Reference remove() throws InterruptedException
キューから次の参照オブジェクトを取り除いて返す。参照オブジェクトが取り出せるようになるまで待つ
public Reference remove(long timeout) throws InterruptedException
キューから次の参照オブジェクトを取り除いて返す。参照オブジェクトが取り出せるようになるまで待つがtimeoutする

以下のようにリソースのインターフェースを定義する。

interface Resource {
    void use(Object key, Object... args);
    void release();
}

インターフェースを実装しResourceImplを以下のように実装する。

private static class ResourceImpl implements Resource {
    int keyHash;
    boolean needsRelease = false;
    ResourceImpl(Object key) {
        keyHash = System.identityHashCode(key);
        // ...外部リソースの設定
        needsRelease = true;
    }
    public void use(Object key, Object... args) {
        if(System.identityHashCode(key) != keyHash) {
            throw new IllegalArgumentException("wrong key");
        }
        // ..外部リソースの使用
    }
    public synchronized void release() {
        if(needsRelease) {
            needsRelease = false;
        }
        // ..外部リソースの解放
    }
}

以下のようにResourceManagerを実装する。

public final class ResourceManager {
    final ReferenceQueue<Object> queue;
    final Map<Reference<?>, Resource> refs;
    final Thread reaper;
    boolean shutdown = false;
    public ResourceManager() {
        queue = new ReferenceQueue<Object>();
        refs = new HashMap<Reference<?>, Resource>();
        reaper = new ReaperThread();
        reaper.start();
        // ...リソースの初期化
    }
    public synchronized void shutdown() {
        if(!shutdown) {
            shutdown = true;
            reaper.interrupt();
        }
    }
    public synchronized Resource getResource(Object key) {
        if(shutdown) {
            throw new IllegalStateException();
        }
        Resource resource = new ResourceImpl(key);
        Reference<?> reference = new PhantomReference<Object>(key, queue);//
        refs.put(reference, resource);
        return resource;
    }
    class ReaperThread extends Thread {
        @Override
        public void run() {
            while(true) {
                try {
                    Reference<?> ref = queue.remove();
                    Resource resource = null;
                    synchronized(ResourceManager.this) {
                        resource = refs.get(ref);
                        refs.remove(ref);
                    }
                    resource.release();
                    ref.clear();
                }
                catch (InterruptedException e) {
                    break;
                }
            }
        }
    }
}

コメントはまだありません»

No comments yet.

RSS feed for comments on this post.TrackBack URL

Leave a comment