Java GC について (1)

黄舟
黄舟オリジナル
2017-02-22 10:08:371532ブラウズ

はじめに

C 言語とは異なり、Java メモリ (ヒープ メモリ) の割り当てとリサイクルは JVM ガベージ コレクタによって自動的に完了するため、プログラマがコードをより適切に作成できるようになります。この記事では、HotSpot 仮想マシンを使用します。たとえば、Java GC について説明します。

Java ヒープ メモリ

「JVM メモリについて」の記事では、Java ヒープがすべてのスレッドによって共有されるメモリ領域であり、すべてのオブジェクト インスタンスと配列がヒープ上のメモリに割り当てられることをすでに知っています。効率的なガベージ コレクションを実行するために、仮想マシンはヒープ メモリをヤング ジェネレーション、オールド ジェネレーション、パーマネント ジェネレーションの 3 つの領域に分割します。

Java GC について (1)

新しい世代

新しい世代は、Eden と Survivor Space (S0、S1) で構成されます。サイズは、Eden と Survivor Space のデフォルトの 8:1 パラメータで指定されます。これは、 -XX :SurvivorRatio パラメーターの指定を通じて渡すことができます。たとえば、新しい世代が 10M の場合、Eden には 8M が割り当てられ、S0 と S1 にはそれぞれ 1M が割り当てられます。



エデン: ギリシャ語、エデンの園を意味します。聖書では、エデンの園は楽園を意味します。旧約聖書の創世記の記録によれば、エホバ神は最初の人間であるアダムをご自身の姿に似せて創造されました。それからアダムを使って肋骨の一つが女性、イブを作り、彼らをエデンの園に置きました。
ほとんどの場合、オブジェクトは Eden に割り当てられます。Eden に十分なスペースがない場合、仮想マシンは -XX:+PrintGCDetails パラメーターを提供して、メモリ リサイクル ログを出力するように指示します。ガベージコレクションが発生します。


Survivor: 生存者を意味し、新世代と旧世代の間の緩衝地帯です。
新世代でGC(Minor GC)が発生すると、残っているオブジェクトはS0メモリ領域に移動され、Eden領域はクリアされます。再度Minor GCが発生すると、EdenとS0に残っているオブジェクトが移動されます。 S1メモリ領域へ。

生き残ったオブジェクトは S0 と S1 の間を繰り返し移動します。オブジェクトが Eden から Survivor に移動するか、Survivor 間で移動すると、オブジェクトの GC 経過時間がデフォルトのしきい値の 15 を超えると、オブジェクトは移動されます。古い世代では、パラメーター -XX:MaxTenuringThreshold を通じて GC 経過時間のしきい値を設定できます。


旧世代

旧世代のスペース サイズは、2 つのパラメーター -Xmx と -Xmn の差であり、数回のマイナー GC の後でもまだ生きているオブジェクトを格納するために使用されます。古い世代に十分なスペースがない場合、メジャー GC/フル GC がトリガーされますが、これは一般にマイナー GC よりも 10 倍以上遅くなります。



永続的な生成

JDK8以前のHotSpot実装では、メソッドデータなどのクラスメタデータ、メソッド情報(バイトコード、スタック、変数サイズ)、実行時定数プール、決定されたシンボル参照や仮想メソッドテーブルなどが保存されます。永続世代のデフォルトのサイズは 64M で、64 ビットのデフォルトは 85M です。クラスのメタデータが永続世代のサイズを超えると、そのサイズが設定されます。 OOM 例外がスローされます。


JDK8 の HotSpot では、仮想マシン チームは Java ヒープから永続世代を削除し、クラスのメタデータをメタスペースと呼ばれるローカル メモリ領域 (オフヒープ メモリ) に直接保存しました。

これを行うメリットは何ですか?

経験豊富な学生は、永続世代の調整プロセスが非常に難しいことに気づくでしょう。永続世代のサイズには、クラスの総数、定数プールのサイズなど、あまりにも多くの要素が関係します。さらに、永続世代のサイズを決定するのは非常に困難です。データはフル GC ごとに移動する可能性があります。

JDK8 では、クラスのメタデータはローカル メモリに保存されます。メタスペースの最大割り当て可能領域は、永続世代のメモリ オーバーフローの問題を回避できます。メモリ障害が発生すると、リークによって大量のローカル メモリが占​​有されます。


追記: JDK7 より前の HotSpot では、文字列定数プール内の文字列は永続世代に保存されていたため、一連のパフォーマンス上の問題やメモリ オーバーフロー エラーが発生する可能性がありました。 JDK8 では、文字列参照のみが文字列定数プールに保存されます。

オブジェクトが生存しているかどうかを判断する方法

GC アクションが発生する前に、ヒープ メモリ内のどのオブジェクトが生存しているかを判断する必要があります。一般的に、参照カウント方法と到達可能性分析方法の 2 つの方法があります。


1. 参照カウント方法
オブジェクトに参照カウンタを追加します。オブジェクトが参照されるたびに、カウンタは 1 ずつ減少します。カウンタ値を持つオブジェクト。 0 は再度使用できないことを表します。

参照カウント方法は実装が簡単で、判定が効率的ですが、オブジェクト間の相互参照の問題を解決できません。


public class GCtest {
    private Object instance = null;
    private static final int _10M = 10 * 1 << 20;
    // 一个对象占10M,方便在GC日志中看出是否被回收
    private byte[] bigSize = new byte[_10M];
    public static void main(String[] args) {
        GCtest objA = new GCtest();
        GCtest objB = new GCtest();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        System.gc();
    }
}

-XX:+PrintGC パラメーターを追加すると、実行結果は次のようになります:


[GC (System.gc()) [PSYoungGen: 26982K->1194K(75776K)] 26982K->1202K(249344K), 0.0010103 secs]


从GC日志中可以看出objA和objB虽然相互引用,但是它们所占的内存还是被垃圾收集器回收了。

2、可达性分析法

通过一系列称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,搜索路径称为 “引用链”,以下对象可作为GC Roots:

本地变量表中引用的对象

方法区中静态变量引用的对象

方法区中常量引用的对象

Native方法引用的对象

当一个对象到 GC Roots 没有任何引用链时,意味着该对象可以被回收。

Java GC について (1)

在可达性分析法中,判定一个对象objA是否可回收,至少要经历两次标记过程:

1、如果对象objA到 GC Roots没有引用链,则进行第一次标记。

2、如果对象objA重写了finalize()方法,且还未执行过,那么objA会被插入到F-Queue队列中,由一个虚拟机自动创建的、低优先级的Finalizer线程触发其finalize()方法。finalize()方法是对象逃脱死亡的最后机会,GC会对队列中的对象进行第二次标记,如果objA在finalize()方法中与引用链上的任何一个对象建立联系,那么在第二次标记时,objA会被移出“即将回收”集合。

看看具体实现

public class FinalizerTest {
    public static FinalizerTest object;
    public void isAlive() {
        System.out.println("I&#39;m alive");
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("method finalize is running");
        object = this;
    }
    public static void main(String[] args) throws Exception {
        object = new FinalizerTest();
        // 第一次执行,finalize方法会自救
        object = null;
        System.gc();
        Thread.sleep(500);
        if (object != null) {
            object.isAlive();
        } else {
            System.out.println("I&#39;m dead");
        }
        // 第二次执行,finalize方法已经执行过
        object = null;
        System.gc();
        Thread.sleep(500);
        if (object != null) {
            object.isAlive();
        } else {
            System.out.println("I&#39;m dead");
        }
    }
}

执行结果:

method finalize is running
I&#39;m alive
I&#39;m dead

从执行结果可以看出:

第一次发生GC时,finalize方法的确执行了,并且在被回收之前成功逃脱;

第二次发生GC时,由于finalize方法只会被JVM调用一次,object被回收。

当然了,在实际项目中应该尽量避免使用finalize方法。

Java GC 的那些事(1)

Java GC的那些事(2)

以上就是Java GC 的那些事(1)的内容,更多相关内容请关注PHP中文网(www.php.cn)!


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
前の記事:Javaでの例外処理次の記事:Javaでの例外処理