Im Gegensatz zur C-Sprache wird die Zuweisung und Wiederverwendung von Java-Speicher (Heap-Speicher) automatisch vom JVM-Garbage Collector durchgeführt. Diese Funktion ist sehr beliebt und kann Programmierern dabei helfen, Code besser zu schreiben Nehmen Sie die virtuelle HotSpot-Maschine als Beispiel, um über Java GC zu sprechen.
Im Artikel über JVM-Speicher wissen wir bereits, dass der Java-Heap ein Speicherbereich ist, der von allen Threads gemeinsam genutzt wird, einschließlich aller Objektinstanzen und Arrays Die gesamte Speicherzuweisung erfolgt auf dem Heap. Um eine effiziente Speicherbereinigung durchzuführen, unterteilt die virtuelle Maschine den Heap-Speicher in drei Bereiche: Junge Generation, Alte Generation und Permanente Generation.
Die neue Generation besteht aus Eden und Survivor Space (S0, S1), und die Größe wird durch angegeben Der Parameter -Xmn beträgt standardmäßig 8:1 und kann über den Parameter -XX:SurvivorRatio angegeben werden. Wenn die neue Generation beispielsweise 10 MB groß ist, werden Eden 8 MB und S0 zugewiesen S1 wird jeweils 1M zugewiesen.
Eden: Griechisch, was „Garten Eden“ bedeutet. In der Bibel bedeutet „Garten Eden“ das Paradies. Den Aufzeichnungen im Alten Testament zufolge erschuf Gott die erste Welt Ein Mann, Adam, erschuf aus einer von Adams Rippen eine Frau, Eva, und platzierte sie im Garten Eden.
In den meisten Fällen werden Objekte in Eden zugewiesen. Wenn Eden nicht über genügend Speicherplatz verfügt, wird ein Minor GC ausgelöst. Die virtuelle Maschine stellt den Parameter -XX:+PrintGCDetails bereit, um der virtuellen Maschine mitzuteilen, wann sie drucken soll Es erfolgt eine Speicherbereinigung.
Überlebender: Es bedeutet Überlebender und ist der Pufferbereich zwischen der neuen Generation und der alten Generation.
Wenn GC (Minor GC) in der neuen Generation auftritt, werden die überlebenden Objekte in den S0-Speicherbereich verschoben und der Eden-Bereich wird gelöscht. Wenn der Minor GC erneut auftritt, werden die überlebenden Objekte in Eden und S0 wird in den Speicherbereich S1 verschoben.
Überlebende Objekte bewegen sich wiederholt zwischen S0 und S1. Wenn das Objekt von Eden nach Survivor oder zwischen Survivors wechselt, wird das GC-Alter automatisch akkumuliert Um das Objekt in die alte Generation zu verschieben, können Sie den GC-Altersschwellenwert über den Parameter -XX:MaxTenuringThreshold festlegen.
Die Speicherplatzgröße der alten Generation ist die Differenz zwischen den beiden Parametern -Xmx und -Xmn, die zum Speichern noch lebender Objekte verwendet wird nach mehreren Minor GCs. Wenn in der alten Generation nicht genügend Speicherplatz vorhanden ist, wird Major GC/Full GC ausgelöst, was im Allgemeinen mehr als zehnmal langsamer ist als Minor GC.
In der HotSpot-Implementierung vor JDK8 werden die Metadaten der Klasse wie Methodendaten, Methodeninformationen (Bytecode, Stapel- und Variablengröße) und Laufzeit verwendet Konstantenpool, ermittelte Symbolreferenzen und virtuelle Methodentabellen werden in der permanenten Generation gespeichert. Die Standardgröße der permanenten Generation beträgt 64 MB für 64 Bit. Sie kann über den Parameter -XX:MaxPermSize festgelegt werden Die Klasse ist: Wenn die Metadaten die permanente Generierungsgröße überschreiten, wird eine OOM-Ausnahme ausgelöst.
In HotSpot von JDK8 entfernte das Virtual-Machine-Team die permanente Generation aus dem Java-Heap und speicherte die Metadaten der Klasse direkt im lokalen Speicherbereich (Off-Heap-Speicher), der als Metaspace bezeichnet wird.
Welche Vorteile hat dies?
Erfahrene Schüler werden feststellen, dass der Optimierungsprozess der permanenten Generation sehr schwierig ist. Die Größe der permanenten Generation ist schwer zu bestimmen, da zu viele Faktoren eine Rolle spielen, wie z. B. die Gesamtzahl der Klassen und die Größe des konstanten Pools, der Anzahl der Methoden usw. und die Daten in der permanenten Generation können sich mit jedem vollständigen GC verschieben.
In JDK8 werden die Metadaten der Klasse im lokalen Speicher gespeichert. Der maximal zuweisbare Speicherplatz des Metaspace ist der verfügbare Speicherplatz des Systems, wodurch das Speicherüberlaufproblem der permanenten Generierung, aber der Speicherverbrauch vermieden werden kann muss überwacht werden. Sobald ein Speicherverlust auftritt, wird eine große Menge lokaler Speicher belegt.
ps: In HotSpot vor JDK7 wurden die Zeichenfolgen im String-Konstantenpool in der permanenten Generation gespeichert, was zu einer Reihe von Leistungsproblemen und Speicherüberlauffehlern führen kann. In JDK8 werden nur String-Referenzen im String-Konstantenpool gespeichert.
Bevor die GC-Aktion ausgeführt wird, müssen Sie feststellen, welche Objekte im Heap-Speicher aktiv sind. Im Allgemeinen gibt es zwei Methoden: Referenzzählung und verfügbare Expressive-Analysemethode.
1. Referenzzählmethode
Immer wenn ein Objekt darauf verweist, erhöht sich der Zähler um 1. Wenn das Objekt aufgebraucht ist, , wird der Zähler um 1 dekrementiert und ein Objekt mit einem Zählerwert von 0 zeigt an, dass es nicht mehr verwendet werden kann.
Die Referenzzählmethode ist einfach zu implementieren und effizient in der Bestimmung, kann jedoch das Problem der gegenseitigen Referenzen zwischen Objekten nicht lösen.
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(); } }
Durch Hinzufügen des Parameters -XX:+PrintGC lautet das laufende Ergebnis:
[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)!