C 言語とは異なり、Java メモリ (ヒープ メモリ) の割り当てとリサイクルは JVM ガベージ コレクタによって自動的に完了するため、プログラマがコードをより適切に作成できるようになります。この記事では、HotSpot 仮想マシンを使用します。たとえば、Java GC について説明します。
「JVM メモリについて」の記事では、Java ヒープがすべてのスレッドによって共有されるメモリ領域であり、すべてのオブジェクト インスタンスと配列がヒープ上のメモリに割り当てられることをすでに知っています。効率的なガベージ コレクションを実行するために、仮想マシンはヒープ メモリをヤング ジェネレーション、オールド ジェネレーション、パーマネント ジェネレーションの 3 つの領域に分割します。
エデン: ギリシャ語、エデンの園を意味します。聖書では、エデンの園は楽園を意味します。旧約聖書の創世記の記録によれば、エホバ神は最初の人間であるアダムをご自身の姿に似せて創造されました。それからアダムを使って肋骨の一つが女性、イブを作り、彼らをエデンの園に置きました。
ほとんどの場合、オブジェクトは 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 経過時間のしきい値を設定できます。
JDK8 の HotSpot では、仮想マシン チームは Java ヒープから永続世代を削除し、クラスのメタデータをメタスペースと呼ばれるローカル メモリ領域 (オフヒープ メモリ) に直接保存しました。
これを行うメリットは何ですか?
経験豊富な学生は、永続世代の調整プロセスが非常に難しいことに気づくでしょう。永続世代のサイズには、クラスの総数、定数プールのサイズなど、あまりにも多くの要素が関係します。さらに、永続世代のサイズを決定するのは非常に困難です。データはフル GC ごとに移動する可能性があります。
JDK8 では、クラスのメタデータはローカル メモリに保存されます。メタスペースの最大割り当て可能領域は、永続世代のメモリ オーバーフローの問題を回避できます。メモリ障害が発生すると、リークによって大量のローカル メモリが占有されます。
追記: JDK7 より前の HotSpot では、文字列定数プール内の文字列は永続世代に保存されていたため、一連のパフォーマンス上の問題やメモリ オーバーフロー エラーが発生する可能性がありました。 JDK8 では、文字列参照のみが文字列定数プールに保存されます。
オブジェクトが生存しているかどうかを判断する方法
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(); } }
[GC (System.gc()) [PSYoungGen: 26982K->1194K(75776K)] 26982K->1202K(249344K), 0.0010103 secs]
从GC日志中可以看出objA和objB虽然相互引用,但是它们所占的内存还是被垃圾收集器回收了。
2、可达性分析法
通过一系列称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,搜索路径称为 “引用链”,以下对象可作为GC Roots:
本地变量表中引用的对象
方法区中静态变量引用的对象
方法区中常量引用的对象
Native方法引用的对象
当一个对象到 GC Roots 没有任何引用链时,意味着该对象可以被回收。
在可达性分析法中,判定一个对象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'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'm dead"); } // 第二次执行,finalize方法已经执行过 object = null; System.gc(); Thread.sleep(500); if (object != null) { object.isAlive(); } else { System.out.println("I'm dead"); } } }
执行结果:
method finalize is running I'm alive I'm dead
从执行结果可以看出:
第一次发生GC时,finalize方法的确执行了,并且在被回收之前成功逃脱;
第二次发生GC时,由于finalize方法只会被JVM调用一次,object被回收。
当然了,在实际项目中应该尽量避免使用finalize方法。
Java GC 的那些事(1)
Java GC的那些事(2)
以上就是Java GC 的那些事(1)的内容,更多相关内容请关注PHP中文网(www.php.cn)!