Java und der GC

ChristianR
Beiträge: 17
Registriert: So 6. Nov 2011, 13:33

Java und der GC

Beitragvon ChristianR » Di 7. Feb 2012, 14:25

Guten Nachmittag,

derzeit versuche ich, dass der Client die vorgegebenen zwei Sekunden möglichst effizient nutzt, bevor er einen Zug abschickt.

Dabei arbeitet er im Prinzip wie folgt:

Code: Alles auswählen

Watch w = new Watch();
w.start();
while (w.getElapsedMilliseconds() < 1900)
{
    // Berechnung durchführen
}
w.stop();
// Zug senden


Hier macht mir leider der Garbage Collector von Java einen Strich durch die Rechnung.
Setzt der GC kurz vor dem Ablauf des 1,9 Sekunden-Intervalls ein, sammelt er auch gerne mal eine halbe Sekunde lange zurückgelassene Objekte ein. Dadurch wird der aktuelle Thread eben diese 0,5 Sekunden auf Eis gelegt.
Die nächste Intervallprüfung erfolgt dann erst bei 2,3 - 2,4 Sekunden. Damit wäre die KI dann draußen.

Ich schlage mich zur Zeit mit VM-Befehlszeilenargumenten für den GC rum, um dieses Problem zu umgehen, doch ist es mir bis jetzt noch nicht gelungen, die Pause-Zeiten durch den GC auf akzeptable Werte runterzubrechen (50-100 ms).

Kennt jemand eine Möglichkeit, den GC für einen kleinen Zeitraum zu deaktivieren und selber zu kontrollieren, dass keine neuen Objekte erzeugt werden, wenn ein gewisser Anteil des maximal verfügbaren Speichers gefüllt wurde?

Edit: Bei weiteren Recherchen bin ich auf RT-Java (RealTime-Java) gestoßen. Liege ich allerdings mit der Annahme richtig, dass die verwendeten VMs RT nicht unterstützen?

Grüße,
Christian

Benutzeravatar
Christian Wulf
Beiträge: 27
Registriert: Mi 31. Aug 2011, 05:54

Re: Java und der GC

Beitragvon Christian Wulf » So 12. Feb 2012, 18:19

Ich denke, du gehst hier das Problem falsch an. Du möchtest eigentlich nur annähernd millisekundengenau deinen aktuell besten Zug senden. Wenn dein Programm oder der Garbage Collector im Hintergrund noch weiterrechnet, stört es dich ja im Grunde genommen gar nicht.
Für solche Anwendungsfälle nutzt man Threads, die ein reaktives Verhalten gewährleisten. Ein Thread könnte dann also den aktuell besten Zug nach z.B. 1,7 sek. aus deinem Algorithmus auslesen und diesen dann an den Server senden. Wann dein Algorithmus dann letztlich terminiert - sei es nach 2,3 sek. oder sogar erst nach 3,0 sek. - kann dir dann egal sein.

ChristianR
Beiträge: 17
Registriert: So 6. Nov 2011, 13:33

Re: Java und der GC

Beitragvon ChristianR » Mo 13. Feb 2012, 12:45

Vielen Dank für die Antwort, allerdings sagen meine Erfahrungen mit dem GC derzeit was anderes.

Für solche Anwendungsfälle nutzt man Threads

Ich benutze bereits Threads.

Die im Startbeitrag angedeuten Codezeilen stehen in der compute-Methode:

Code: Alles auswählen

_ComputingThread = new Thread(new Runnable()
      {
         public void run()
         {
            compute(); // Hier werden Berechnungen durchgeführt
         }
      });
      _ComputingThread.setDaemon(true);
      _ComputingThread.setName(this.getClass().getName() + ".ComputingThread");




Der GC hat die Eigenschaft, in mehreren Phasen (je nach VM-Argumenten) einmal den Speicher freizuräumen.
Dafür beansprucht er ein Zeitintervall, in dem die Ausführung aller laufenden Threads unterbrochen wird.

There are two primary measures of garbage collection performance. Throughput is the percentage of total time not spent in garbage collection, considered over long periods of time. Throughput includes time spent in allocation (but tuning for speed of allocation is generally not needed.) Pauses are the times when an application appears unresponsive because garbage collection is occurring.

Quelle: http://www.oracle.com/technetwork/java/ ... 38395.html

Traditional GC implementations use a stop-the-world (STW) approach to recovering heap memory. An application runs until the heap is exhausted of free memory, at which point the GC stops all application code, performs a garbage collect, and then lets the application continue.

Quelle: http://www.ibm.com/developerworks/java/library/j-rtj4

Das Problem sind in diesem Fall die im Zitat fett markierten Pausen.
Mit dem -verbose:gc-Befehlszeilenparameter habe ich bereits das Verhalten des GCs analysiert.
Bei einem festgelegten Speicher von über 1GB (meines Wissens steht der KI 1,5GB zur Verfügung) können die Pause-Zeiten - zumindest bei mir - auch mal eine gute Sekunde lang sein.

Es hat sich bei der Analyse mit "verbose:gc" auch gezeigt, dass die KI wirklich nur dann länger als 1,9 Sekunden rechnet, wenn der GC gerade aufräumt (verbose sorgt des Weiteren für die Ausgabe der Information, wie lange während eines Collects des GCs pausiert wurde. Ziehe ich diesen Wert nachher von der Zeit ab, wie lange das Senden des Zuges real verzögert wurde, komme ich nie auf einen Wert größer 1,9)

Die Situation sieht dann ungefähr wie folgt aus:

Sekunde 0: Zug wird angefordert, Berechnungen folgen
Sekunde 1,8: Der GC räumt auf - Pause-Time bei einer 1 Sekunde
(Sekunde 1,9: Hier hätte der Thread nun reagieren und den Zug losschicken sollen)
Sekunde 2,8: Pause-Time ist vorbei. Der Thread realisiert: 2,8 > 1,9. Der Zug wird losgeschickt. Bis hier hin wäre allerdings mit Zeitbegrenzung längst ein Timeout der Fall gewesen.

Benutzeravatar
Christian Wulf
Beiträge: 27
Registriert: Mi 31. Aug 2011, 05:54

Re: Java und der GC

Beitragvon Christian Wulf » Mi 22. Feb 2012, 09:15

Es ist natürlich korrekt, dass der GC jederzeit und auch sehr lange aufräumen kann, allerdings solltet ihr in den 2 Sekunden nicht so viele Ressourcen zum Aufräumen erzeugen können.

heap is exhausted of free memory


Ihr könnt zu Testzwecken mal die maximale Heapgröße erhöhen. Dazu startet ihr eure KI mit dem folgenden Befehl:

Code: Alles auswählen

java -jar KI.jar -Xmx1024m


Der letzte Parameter setzt hier die Heapgröße auf 1024 Mb.

Sobald aber Threads in ein Programm integriert werden, können viele Ursachen für das Problem möglich sein. Wie greift ihr bspw. aus der Methode compute() auf Ressourcen außerhalb des Threads zu? Ihr müsst dort immer extrem auf Synchronisierung achten!

Wiei viele Threads für welche Zwecke nutzt du denn?

Gruß
Christian

ChristianR
Beiträge: 17
Registriert: So 6. Nov 2011, 13:33

Re: Java und der GC

Beitragvon ChristianR » Do 23. Feb 2012, 18:19

Es läuft genau ein Thread, dessen Aufgabe es ist, die benötigten Daten-Objekte zur Ermittlung des nächsten Zuges zu erzeugen.
Auf Synchronisation ist strikt mit einem OOP-Ansatz geachtet. Zugriffe auf die Daten erfolgen immer vom Thread selber aus, der in einer While-Schleife eingegangene Anfragen verarbeitet (eine Art Dispatcher-Prinzip).
Die einzig hier nötige Synchronisation ist der Zugriff auf die Liste, in der die Request-Objekte abgelegt werden. Auf die Liste kann dementsprechend nur synchronisiert zugegriffen werden.

Die Daten, auf der die Compute-Methode arbeitet, sind also nur für diese zugänglich.


Ich habe seit jeher den Heap bereits auf 1024MB festgelegt.
Da aber nach jedem Rechenzug nicht alle berechneten Daten verworfen werden, konnte es auch mal passieren, dass wir die 90%-Hürde geknackt haben (über 900MB Speicher).

Am Anfang war dies extrem, da die Referenzen auf nicht mehr benötigte Objekte nicht korrekt verworfen wurden, inzwischen bin ich mir aber zu 101% sicher, dass keine Speicher-Leaks mehr vorhanden sind, da ansonsten die KI den Geist aufgeben würde.

Zur der Aufräumaktivität des GCs: Das bisher "fatalste" Ergebnis, dass wir festgestellt haben, war eine Verzögerung von 0,348 Sekunden bei einem Test-Spiel nach dem Hochladen der KI.
Da nur schwer abschätzbar ist, wie sich diese Verzögerung verändert, wenn nicht gegen die SimpleClient-KI gespielt wird, werden wir die Reaktionszeit nochmal herabsetzen müssen.

Die oben genannte Verzögerung tratt ein, als die KI nach 1,5 Sekunden den Zug losschicken sollte.