Ich werde zusammenfassen, was ich über den Mechanismus des JIT-Compilers der JVM gelernt habe.
~/workspace/$ java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_222-b10)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.222-b10, mixed mode)
Just In Time-Compiler, der auf JVM implementiert ist. Just In Time, ein Compiler, der kompiliert, "was Sie brauchen, wann Sie es brauchen".
Grob gesagt fließt der folgende
Quellcode->kompilieren->Zwischencode->Ausführen (kompilieren)
Die JVM-Sprache kompiliert nicht den gesamten interessierenden Quellcode, wenn Sie die Kompilierung ausführen (javac
für Java, sbt compile
für scala usw.).
Erstellen Sie zunächst einen Zwischencode. (Java-Byte-Code). Durch Sandwiching des Prozesses zum Generieren dieses Zwischencodes kann derselbe Code auf jedem Betriebssystem ausgeführt werden, solange eine JVM-Umgebung vorhanden ist. (JVM muss für jedes Betriebssystem geeignet sein)
Danach wird der erstellte Zwischencode nicht sofort kompiliert (in nativen Code konvertiert). Der Interpreter kompiliert den Quellcode bei jeder Ausführung. (Java-Interpreter in der obigen Abbildung)
Dafür gibt es zwei Gründe.
Da das Kompilieren für Code, der nur einmal aufgerufen wird, einige Zeit in Anspruch nimmt, verkürzt die Ausführung mit dem Interpreter die Gesamtzeit für die Ausführung. Andererseits kann kompilierter Code für häufig aufgerufenen Code schneller ausgeführt werden und sollte kompiliert werden. Die Erörterung des Schwellenwerts in der JVM, ob mit dem Interpreter ausgeführt oder kompiliert und ausgeführt werden soll, wird später beschrieben.
Sie können die Informationen erhalten, die Sie kompilieren müssen, wenn Sie sie im Interpreter ausführen. Unter Verwendung der erfassten Informationen können zum Zeitpunkt der Kompilierung verschiedene Optimierungen durchgeführt werden. Diese Optimierung macht auch innerhalb dieses kompilierten Codes einen Unterschied in der Ausführungszeit.
Betrachten Sie zum Beispiel die equals () -Methode
Ich habe den folgenden Code.
test.scala
val b = obj1.equals(obj2)
Wenn der Interpreter die Methode "equals ()" erreicht, muss gesucht werden, ob die Methode "equals ()" die in obj1 definierte Methode oder die Methode des String-Objekts ist. Mit nur dem Interpreter wäre es Zeitverschwendung, jedes Mal zu suchen, wenn die Methode "equals ()" erreicht wird.
Wenn der Interpreter feststellt, dass obj1 eine Methode eines String-Objekts ist, kompiliert er die Methode equals ()
als Methode des String-Objekts. Es kompiliert und eliminiert die Zeit, die es beim Interpretieren für das Erkunden benötigt, was zu einem schnelleren Code führt.
Wie Sie sehen können, kompiliert der JIT-Compiler den Code nicht sofort, da er nicht optimiert werden kann, ohne den Code auszuführen und zu betrachten.
Es gibt drei Arten von JIT-Compilern. Ab Java8 ist der dritte hierarchische Compiler standardmäßig festgelegt.
Frühzeitig kompilieren
Sammeln Sie Informationen zum Verhalten Ihres Codes, bevor Sie kompilieren. Wie oben erwähnt, ist es optimiert und kompiliert, sodass es schneller als der Client-Compiler ist.
Eine Sammlung von Client-Compilern und Server-Compilern. In der Anfangsphase wird es in C1 kompiliert, und wenn die Informationen zur Optimierung gesammelt werden (wenn der Code heiß wird), wird es in C2 kompiliert.
Sie können den konfigurierten Compiler mit Java-Version überprüfen In meinem Fall
~/workspace/$ java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_222-b10)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.222-b10, mixed mode)
Jeder Compiler sollte abhängig von der zu erstellenden Anwendung ordnungsgemäß verwendet werden. Wenn Sie beispielsweise eine GUI-Anwendung auf einer JVM ausführen, sollten Sie einen Client-Compiler verwenden, da es für UX besser ist, eine schnellere anfängliche Zugriffszeit zu haben, als die Verarbeitungsgeschwindigkeit zu erhöhen, wenn Sie sie verwenden.
Wählen Sie den Compiler entsprechend der Anwendung und dem, was Sie realisieren möchten.
Wie oben erwähnt, wird der Bytecode zuerst vom Interpreter ausgeführt. Zu welchem Zeitpunkt können Sie dann vom Interpreter zum JIT-Compiler wechseln? Es gibt zwei Schwellenwerte
Häufigkeit, mit der die Zielmethode aufgerufen wurde
Wenn diese Anzahl den Schwellenwert überschreitet, wird die Zielmethode zur Kompilierung in die Warteschlange gestellt und kompiliert.
Die Häufigkeit, mit der die Verarbeitung vom Code in der Schleife innerhalb der Methode zurückgegeben wird
Wenn diese Zahl den Schwellenwert überschreitet, wird die Schleife selbst zum Ziel der Kompilierung und wird kompiliert. Die zu diesem Zeitpunkt durchgeführte Kompilierung wird als OSR-Kompilierung bezeichnet. Wenn die Kompilierung abgeschlossen ist, wird sie gegen den auf dem Stapel kompilierten Code ausgetauscht, und der kompilierte Code wird ab dem nächsten Prozess ausgeführt.
Die oben genannten Zählerschwellenwerte unterscheiden sich zwischen dem Client-Compiler und dem Server-Compiler. Sie müssen diesen Schwellenwert richtig einstellen.
Wenn Sie den Schwellenwert für die Standardkompilierung mit dem Server-Compiler senken, werden die für die Kompilierung erforderlichen Informationen reduziert, was die Optimierung erschwert und zu einem langsamen Kompilierungscode führt.
Dennoch ist es sinnvoll, die Schwelle zu senken
Korrekt.
In dieser Hinsicht ist es wahrscheinlich, dass der Schwellenwert für den Anrufzähler / Beutelkantenzähler schließlich erreicht wird, wenn der Code weiter ausgeführt wird. Der Zählerwert wird jedoch über die Zeit abgezogen.
Wie oben erwähnt, müssen Sie richtig einstellen.
Beobachten und optimieren Sie das Verhalten des JIT-Compilers mit dem folgenden Code
Sie können die Option jvm in .jvmopts
wie unten gezeigt angeben.
.jvmopts
-XX:+PrintCompilation
-XX:CompileThreshold=1
-XX:+PrintCompilation
Es spuckt das Kompilierungsprotokoll wie unten gezeigt aus
Das Format ist
Zeitstempel-Kompilierung ID Attribut Methode Name Größe Deoptimierung
$ sbt run
41 1 3 java.lang.Object::<init> (1 bytes)
42 2 3 java.lang.String::hashCode (55 bytes)
44 3 3 java.lang.String::charAt (29 bytes)
45 4 3 java.lang.String::equals (81 bytes)
45 5 n 0 java.lang.System::arraycopy (native) (static)
45 6 3 java.lang.Math::min (11 bytes)
45 7 3 java.lang.String::length (6 bytes)
52 8 1 java.lang.Object::<init> (1 bytes)
52 1 3 java.lang.Object::<init> (1 bytes) made not entrant
53 9 3 java.util.jar.Attributes$Name::isValid (32 bytes)
53 10 3 java.util.jar.Attributes$Name::isAlpha (30 bytes)
・ ・ ・ ・
XX:CompileThreshold=1000
Sie können angeben, wie oft die Methodenschleife ausgeführt wird, bevor sie kompiliert wird.
Versuchen Sie es mit dem folgenden Code
Test.scala
object Test extends App{
def compileTest() = {
for (i <- 0 to 1000) {
sampleLoop(i)
}
}
def sampleLoop(num: Int) = {
println(s"loopppp${num}")
}
println(compileTest())
}
.jvmopts
-XX:+PrintCompilation
-XX:CompileThreshold=1
Ergebnis
Da ich "-XX: CompileThreshold = 1" gesetzt habe, kann ich bestätigen, dass die "compileTest" -Methode kompiliert wurde, indem ich diesen Code einmal ausführe.
Die sampleLoop
-Methode ist ebenfalls eine Schleife, daher wird sie kompiliert.
9983 9336 3 Test$$$Lambda$3666/873055587::apply$mcVI$sp (5 bytes)
9983 9338 3 Test$::sampleLoop (1 bytes)
9983 9337 3 Test$::$anonfun$compileTest$1 (8 bytes)
9984 9334 4 java.lang.invoke.MethodType::makeImpl (66 bytes)
9986 9339 ! 3 scala.Enumeration$Val::toString (55 bytes)
・ ・ ・
Die compileTest
-Methode wird 9 Sekunden nach dem Start der JVM kompiliert.
Was ist zum Beispiel mit den folgenden Einstellungen?
object Test extends App{
def compileTests() = {
for (i <- 0 to 10) { //Wechseln Sie zu 10 Schleifen
sampleLoop(i)
}
}
def sampleLoop(num: Int) = {
println(s"loopppp${num}")
}
println(compileTests())
}
.jvmopts
-XX:+PrintCompilation
-XX:CompileThreshold=100 #Ändern Sie den Schwellenwert auf 100 Mal
Wenn Sie "-XX: CompileThreshold = 100" usw. festlegen, wird die "compileTest" -Methode nicht nur durch einmaliges Ausführen des obigen Codes kompiliert.
Außerdem wird die sampleLoop
-Methode nicht ausgeführt, da sie nicht 100 Mal ausgeführt wird.
Es ist leicht zu verstehen, wenn Sie sich den JIT-Kompilierungsprozess ansehen.
Recommended Posts