2016-07-15 7 views
3

Wir haben eine große JDK7-Anwendung auf JBoss mit mehreren Bibliotheken wie Hibernate, Spring und so weiter bereitgestellt. Nach dem ersten Start des Servers wird die Anwendung wie erwartet ausgeführt, nach einiger Betriebszeit wird sie jedoch sehr langsam.JDK7 Anwendung wird langsam nach einiger Uptime

Mit einem Profiler haben wir gesehen, dass jedes Mal, wenn bestimmte Aspekte der Anwendung sich verlangsamen, aber nicht immer die gleichen Aspekte. In einem Durchlauf könnte es sein, dass der Hibernate-Flush zu einem Crawl verlangsamt wird, in einem anderen Run könnte es sich um einen DI-Code von Spring handeln.

Was ist dort los?

Antwort

7

Es gibt einen Fehler in JDK7 bezüglich des CodeCache Speicherbereichs, der uns sehr, sehr hart getroffen hat.

Erklärung

Grundsätzlich Java gestartet und verwendet nur in Time-Kompilierung (JIT) während der Laufzeit nur die benötigten Teile des Bytecode kompiliert. Dadurch kann die JVM bestimmte Codefragmente während der Ausführung de- und rekompilieren. Dies passiert, wenn die JVM feststellt, dass eine Erstkompilierung eines bestimmten Codefragments suboptimal ist. Oracle führte in JDK 7 eine Funktion namens gestaffelte Kompilierung ein, die es der VM ermöglicht, genau das zu tun.

Kompilierter Code in der JVM wird im Speicherbereich CodeCache gespeichert. Bis JDK6 war der Standard, dass dieser Bereich gefüllt würde und einmal bei 100% würde der JIT aufhören zu kompilieren und ein Fehler würde auf die Konsole gedruckt werden, aber die Anwendung würde genauso laufen wie zuvor: Alles was bereits kompiliert wurde, würde kompiliert bleiben würde alles, was noch nicht kompiliert wurde, im Interpretationsmodus ausgeführt (was ungefähr 100x langsamer ist)

Diese Option heißt CodeCacheFlushing, sie ist standardmäßig seit JDK7u4 aktiviert. Die Idee ist, dass, sobald CodeCache voll ist, die am wenigsten verwendeten Teile des kompilierten Codes aus dem Speicher gelöscht werden, um Platz für andere Codefragmente zu schaffen. Das würde das JDK6-Default-Verhalten (um die Kompilierung insgesamt zu stoppen) obsolet machen. Es erlaubt auch einen viel kleineren CodeCache-Bereich (in JDK7 ist CodeCache standardmäßig 48M/96M, wenn gestaffelte Kompilierung aktiviert ist).

Hier kommt der Fehler. Sobald der CodeCache in JDK7 voll wird, wird der JIT gestoppt. Als nächstes kommt das Löschen des CodeCache-Bereichs. Das ist es. JIT sollte wieder aktiviert werden, nachdem die Spülung abgeschlossen wurde, aber das passiert nicht. Außerdem wird keine Warnung auf der Konsole ausgegeben. Schlimmer noch: Vor der Deaktivierung des JIT wird ungefähr die Hälfte des bereits kompilierten Codes verworfen.

Im Gegensatz zu JDK6, wo alles, was schnell war, schnell bleibt und nur neuer Code interpretiert wird, verlieren Sie in JDK7 tatsächlich bereits kompilierten und optimierten Code! Alle Teile Ihrer Anwendung, die sich gut bewährt haben, werden damit aufhören. Es ist dem Zufall überlassen, welche Teile der Anwendung langsamer werden, wodurch das Verfolgen des Buggers durch den Profiler nahezu unmöglich wird: Manchmal verlangsamt sich der Ruhezustandscode für das Spülen, zu anderen Zeiten ist es der Frühjahrs-DI-Code oder Ihr eigener Appcode.

Sind Sie betroffen?

Sie können einen Profiler (JProfiler/YourKit) oder JConsole (JVisualVM wird nicht tun) verwenden, um den Speicherverbrauch des CodeCache-Speicherbereichs zu überwachen. In der Regel wird die CodeCache-Menge committed sehr nahe an der used Menge bleiben (sagen wir committed ist 23mb, verwendet ist 22mb). Während Ihre Anwendung ausgeführt wird, gehen committed und used hoch, bis committedmax erreicht.An diesem Punkt wird used scharf auf 1/2 - 2/3 von max fallen. Danach wachsen used nicht mehr. Da wird der Bug dich treffen. In JConsole, wird es wie folgt aussehen:

CodeCache Memory Consumption

Warum ich und nicht alle anderen?

Die Chancen stehen gut, dass Sie JBoss verwenden. Oracle fand schnell heraus, dass es Dinge gibt, die nicht so sein sollten, dass sie standardmäßig tiered compilation sein sollten - und doch entschied sich Red Hat in seiner unendlichen Weisheit, es wusste es besser und reaktivierte es erneut. Grundsätzlich funktioniert unsere Webapp in Weblogic gut und nur JBoss ist betroffen, denn ohne die gestufte Kompilierung (nicht in Weblogic aktiviert) ist das Wachstum von CodeCache so gering, dass wir selbst nach wochenlangem Betrieb niemals die Schwelle von 48 MB erreichen.

Was kann ich tun?

Zuerst, entscheiden Sie, ob dieser Fehler Sie trifft. Zweitens, erschweren Sie es, dass der Bug Sie schädigt. Wenn Sie CodeCacheFlushing deaktivieren, wird der Fehler zumindest nicht schlimmer als zuvor. Das Stoppen von tiered compilation macht es weniger wahrscheinlich, dass der Fehler auf Sie stößt, genauso wie das Erhöhen des verfügbaren CodeCache-Speichers.

Sie können immer versuchen, zu JDK8 zu wechseln, dies scheint nicht betroffen zu sein und Sie könnten auch eine Überwachung in Ihrer Software implementieren, um Sie zu warnen, wenn CodeCache voll ausgeführt wird.

TL; DR

  • In JDK 7 nie tiered Kompilierung aktivieren (standardmäßig aktiviert in JBoss deaktiviert)
  • in JBoss 7 immer gesetzt PRESERVE_JAVA_OPTS=true in standalone.conf
  • immer deaktivieren CodeCacheFlushing (-XX:-UseCodeCacheFlushing)
  • immer packen Sie eine ausreichende Menge an Speicher in CodeCache (-XX:ReservedCodeCacheSize=xxM).
+2

Link zum JDK-Problem: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8012547 –

Verwandte Themen