2016-04-18 16 views
1

Wir haben eine Anwendung, die in der Regel ~ 20 JVMs besteht und wir verteilen Batch-Jobs an sie. Die 20 JVMs laufen im selben Betriebssystem. Bevor man einen Stapeljob an einen von ihnen schickt, ist es schwer zu sagen, wie lang und wie groß der Job ist. Es könnte 1 Minute oder mehrere Stunden dauern. Der Speicherverbrauch variiert ähnlich.Garbage Collection für mehrere VMs

Bisher funktionierte das gut, wir haben insgesamt 40GB Speicher zur Verfügung, wir hatten Max Heap Größe auf 2GB für jede JVM (2 GB ist manchmal notwendig). Da es nie der Fall war, dass zu viele "große" Stapeljobs gleichzeitig ausgeführt wurden, gab es nie Speicherprobleme. Bis wir auf Java 8 vm umgezogen sind. Es scheint, dass der vollständige GC weniger häufig ausgelöst wird. Wir haben JVM in der Speicherbelegung meistens im Leerlauf. Wenn ich einen GC durch den Aufruf von jcmd triggere, kann ich sehen, dass der OldGen von 1 GB auf 200 MB sinkt.

Ich weiß, das ist kein gutes Setup, um 20 JVMs mit maximal 2 GB Heap + Stack + Metaspace zu haben, die viel mehr als die verfügbaren 40 GB Speicher wären. Aber es ist eine Situation, mit der wir leben müssen. Und ich wäre überrascht, wenn es eine Möglichkeit gibt, eine maximale Heap-Größe für einen Cluster von mehreren JVMs festzulegen. Also muss ich mir andere Lösungen ausdenken.

Ich war auf der Suche nach einer VM-Option, die die VM anweist, einen vollständigen GC in regelmäßigen Abständen zu machen, dies würde sehr wahrscheinlich unser Problem lösen. Aber ich kann keine VM Option dafür finden.

Irgendwelche Vorschläge, wie wir dies einrichten können, um Speicherwechsel zu vermeiden?

EDIT: Hier ist ein Ausschnitt aus dem gc-Log:

2016-04-14T01:02:49.413+0200: 37428.762: [Full GC (Ergonomics) [PSYoungGen: 28612K->0K(629248K)] [ParOldGen: 1268473K->243392K(1309184K)] 1297086K->243392K(1938432K), [Metaspace: 120332K->120320K(1181696K)], 0.3438924 secs] [Times: user=1.69 sys=0.02, real=0.35 secs] 
2016-04-14T01:02:52.442+0200: 37431.792: [GC (Allocation Failure) [PSYoungGen: 561664K->67304K(629248K)] 805056K->310696K(1938432K), 0.0315138 secs] [Times: user=0.26 sys=0.00, real=0.03 secs] 
2016-04-14T01:02:54.809+0200: 37434.159: [GC (Allocation Failure) [PSYoungGen: 628968K->38733K(623104K)] 872360K->309555K(1932288K), 0.0425780 secs] [Times: user=0.35 sys=0.00, real=0.04 secs] 
... 
2016-04-14T10:09:03.558+0200: 70202.907: [GC (Allocation Failure) [PSYoungGen: 547152K->41386K(531968K)] 1545772K->1041036K(1841152K), 0.0255883 secs] [Times: user=0.18 sys=0.00, real=0.02 secs] 
2016-04-14T10:20:53.634+0200: 70912.984: [GC (Allocation Failure) [PSYoungGen: 531882K->40733K(542720K)] 1531532K->1042107K(1851904K), 0.0306816 secs] [Times: user=0.22 sys=0.02, real=0.03 secs] 
2016-04-14T10:23:10.830+0200: 71050.180: [GC (System.gc()) [PSYoungGen: 60415K->37236K(520192K)] 1061790K->1040674K(1829376K), 0.0228505 secs] [Times: user=0.17 sys=0.01, real=0.02 secs] 
2016-04-14T10:23:10.853+0200: 71050.203: [Full GC (System.gc()) [PSYoungGen: 37236K->0K(520192K)] [ParOldGen: 1003438K->170089K(1309184K)] 1040674K->170089K(1829376K), [Metaspace: 133559K->129636K(1196032K)], 1.4149811 secs] [Times: user=11.10 sys=0.02, real=1.42 secs] 

Wenn wir eine vollständige GC jede Stunde hätte, wäre es unser Problem zu lösen, denke ich.

+0

System.gc()? Aber Vorsicht: Es ist nicht garantiert, irgendetwas zu tun, normalerweise nennt man es nicht die Lösung. außerdem: Objekte altern, ich denke, dass sehr alte Objekte für immer im Speicher bleiben ... – Exceptyon

+2

Warum müssen Sie einen vollständigen GC erzwingen, anstatt den G1-Kollektor einen vollständigen GC ausführen zu lassen, wenn er sich entscheidet? Sie haben gesagt, dass es 2 GB Heap verwenden kann und Sie versuchen, einen vollständigen GC auszulösen, wenn es nur 1 GB verwendet. Das scheint seltsam. Vielleicht möchten Sie tatsächlich Ihre JVM optimieren, so dass weniger Objekte in der alten Generation landen - angesichts Ihrer Nutzungsmuster, nehme ich an, dass Ihre großen, langlebigen Chargen die alte Generation bevölkern, wenn Sie es wahrscheinlich lieber nicht tun würden . – sisyphus

+1

@Exceptyon Nein, sie tun es ganz sicher nicht. Nicht erreichbare Objekte werden gesammelt, unabhängig vom Alter. – Kayaman

Antwort

1

Anstatt zeitgesteuerte GCs zu verwenden, können Sie versuchen, mit -XX:GCTimeRatio=14 -XX:MaxHeapFreeRatio=30 -XX:MixHeapFreeRatio=20 zu laufen. Dies wird dem Sammler sagen, dass er weniger Spielraum haben muss, indem er es erlaubt, öfter zu sammeln/mehr CPU-Zyklen auf GCs zu verbringen.

Bei aktuellen JDK9-Builds könnte dies weiter mit -XX:-ShrinkHeapInSteps kombiniert werden, damit die zugewiesene Heap-Größe den verwendeten Heap noch genauer verfolgen kann. Auch dies möglicherweise auf Kosten der Leistung.

+0

soweit mein Verständnis der GCTimeRatio war, dass dies die Zeit mit GC begrenzt werden sollte. Kann ich diese Option tatsächlich verwenden, um der VM mitzuteilen, dass sie mehr Zeit mit GC verbringen soll? – EasterBunnyBugSmasher

+0

nicht ganz, aber der Kollektor hat ein Implikatur-Footprint-Ziel, das erst nach der Pausenzeit und der Zeitverhältniseinschränkung berücksichtigt wird. Da der Parallelkollektor über kein Pausenzeitziel verfügt, das standardmäßig konfiguriert ist, wird die Zeitverhältniseinschränkung durch das entspannte Zeitverhältnis aggressiver eingestellt, um das Durchsatzziel zu erreichen. kombiniert mit den anderen Optionen, die zu häufigeren Sammlungen führen können. – the8472

2

Es macht keinen Sinn, einen GC zu zufälligen Zeiten zu machen.

Ich würde den GC am Ende einer Charge hinzufügen (oder danach). Es ist an diesem Punkt, dass der geringste Speicher wahrscheinlich beibehalten werden muss, wodurch der GC schneller wird und die beste Schrumpfung erhalten wird.

0

Danke an alle Antworten/Kommentare. Die Lösung, die ich mir ausgedacht habe, ist eine Kombination aus vielen Antworten/Kommentaren.

@Peter Lawrey: Aufruf von System.gc() nach jedem Batch-Lauf macht sehr viel Sinn und ich bin erstaunt, dass wir nicht mit dieser früher kommen. Es hat nicht dazu beigetragen, die Speicherbelegung zu verringern. Wir würden nur mit einer 1 GB alten Generation enden, die nur mit 200 MB Daten gefüllt war.

@ the8472: GCTimeRatio schien uns in keiner Weise zu helfen. Aber wir haben MaxHeapFreeRatio und MinHeapFreeRatio beide auf 40 geändert. Die Wahl niedrigerer Werte hat die Größe der jungen Generation zu sehr eingeschränkt und sie ist nie über 200 MB gewachsen. Ich nehme an, dass die Einstellung beider Parameter auf den gleichen Wert eine Menge Speicherzuweisungen und Freigaben verursacht, aber wir machen immer noch eine gute Zeit mit < 1% Zeit in GC verbracht.Wenn Sie viele Datenbankanfragen ausführen, wird der Performance-Impact von GC vernachlässigbar :-)

@Sisyphus: Das Festlegen des newRatio auf 1 half dabei, dass die junge Generation und die alte Generation ähnliche Größen haben. Dies ist wahrscheinlich die Veränderung mit dem höchsten Nutzen.

+0

jetzt, wo wir es für eine Weile laufen hatten: Die Einstellung von newRatio auf 1 war eine schreckliche Idee. Mit 2 GB Heap würde die alte Generation niemals über 1 GB wachsen. Wir würden nicht mehr genügend Arbeitsspeicher zur Verfügung haben, wenn die volle 1GB alte Generation verwendet wurde, aber immer noch 600 + MB freien Arbeitsspeicher in der neuen Generation. – EasterBunnyBugSmasher