O'Reilly Japan \ -Java Performance Résumé du chapitre 4 de ce livre
Chapitre 1 Introduction \ -Qiita Chapitre 2 Approche des tests de performances \ -Qiita Chapitre 3 Java Performance Toolbox \ -Qiita ← Article précédent Chapitre 4 Fonctionnement du compilateur JIT - Qiita ← Cet article Chapitre 5 Bases de la collecte des ordures \ -Qiita ← Article suivant
Lors de la lecture de deux valeurs de la mémoire principale et de leur ajout ... Un bon compilateur exécute une instruction qui lit des données, puis exécute une autre instruction, puis ajoute. Vous ne pouvez pas car l'interprète ne voit qu'une seule ligne à la fois.
Les interprètes ont l'avantage de la portabilité
La nouvelle version du CPU peut exécuter presque toutes les instructions de la version précédente du CPU, mais pas l'inverse (comme les instructions AVX du processeur Sandy Bridge d'Intel). Il existe une solution telle que l'exécution du traitement où les performances sont importantes dans la bibliothèque partagée préparée pour chaque CPU.
Java est dans une position intermédiaire, et après compilation en code octet Java, il est compilé sur chaque plateforme en même temps que l'exécution.
Par exemple, si vous voulez exécuter du code comme b = obj1.equals (obj2)
Pour savoir quelle méthode ʻequals () doit exécuter, regardez le type (classe) de ʻobj1
.
Lorsque ce code est exécuté, si ʻobj1 est toujours appelé
java.lang.String # equals`
Optimisez pour appeler la méthode directement.
Quand le compilateur est-il optimisé pour enregistrer de la mémoire principale vers les registres? Peut être mentionné
public class RegisterTest {
private int sum;
public void calculateSum(int n) {
for (int i = 0; i < n; i++) {
sum += i;
}
}
}
Pour ce code, avant de lire la somme de la mémoire principale L'optimisation peut être effectuée en conservant la somme dans un registre, en la bouclant et en ajoutant le résultat du calcul à additionner dans la mémoire principale.
Le registre utilisé par un autre thread ne peut pas être lu (voir chapitre 9). Les registres sont utilisés de manière particulièrement agressive lorsque l'analyse d'échappement (décrite à la fin de ce chapitre) est activée.
Il existe un type de client et un type de serveur. Il est ainsi appelé par les arguments de ligne de commande -client
et -server
.
Dans la plupart des cas, -XX
n'est pas utilisé comme indicateur pour spécifier le compilateur.
L'exception est la compilation à plusieurs niveaux.
-XX:+TieredCompilation
Un compilateur serveur est requis pour la compilation hiérarchique.
Le compilateur client commence la compilation tôt, donc il est rapide dans les premiers stades. D'autre part, le compilateur serveur prend du temps à s'optimiser.
La compilation hiérarchique est une méthode dans laquelle le compilateur serveur se compile à nouveau lorsque le code devient "chaud". La compilation hiérarchique est activée par défaut dans Java 8.
La compilation hiérarchique en Java 7 est bizarre, par exemple, elle dépasse rapidement la taille du cache de code de la JVM.
Il existe trois versions comme suit
--Compilateur client 32 bits (-client
)
-server
)-d64
)S'il s'agit d'un système d'exploitation 32 bits, la JVM doit également être une version 32 bits. Avec un système d'exploitation 64 bits, vous pouvez utiliser l'une ou l'autre JVM.
Si le tas est de 3 Go ou moins, la version 32 bits utilise moins de mémoire et est plus rapide. Il semble que le 32 bits a un coût de référence mémoire inférieur à celui du 64 bits.
Le chapitre 8 traite de la compression des pointeurs d'objets ordinaires. Une adresse 32 bits peut être utilisée même avec une JVM 64 bits. Cependant, comme le code natif utilisé lors de l'exécution utilise une adresse 64 bits, il utilise beaucoup de mémoire.
Dans les programmes qui utilisent beaucoup les types 8 octets (long, double), la JVM 32 bits est lente car les registres 64 bits du CPU ne peuvent pas être utilisés.
32bitOS a un mur de 4Gbyte (2 ^ 32)
↓ Documents officiels connexes CompressedOops
↓ affiche le compilateur Java par défaut
java -version
↓ Documents officiels connexes java
Lorsque la JVM compile le code, un ensemble d'instructions en langage assembleur est stocké dans le cache de code. La taille du cache de code est fixe. Lorsqu'il est plein, il ne peut plus compiler et fonctionne avec l'interpréteur.
Lors de la compilation hiérarchique avec Java7, la taille par défaut manque souvent de cache de code (certains SIers ne me permettent toujours pas d'élever la version Java à 8 ou plus tard, et dans ce cas, j'ai ce genre de problème ... )
Il n'y a aucun moyen de savoir combien de cache de code votre application a besoin, vous devez donc l'exécuter et vérifier si c'est suffisant (voir ci-dessous pour savoir comment vérifier).
---XX: XX: InitialCodeCacheSize-N
: Taille initiale du cache de code. Il a par défaut 2,555,904 octets dans mon environnement
---XX: XX: ReservedCodeCacheSize = N
: taille maximale. Il est par défaut de 251 658 240 octets dans mon environnement
---XX: XX: CodeCacheExpansionSize = N
: Taille étendue du cache de code. Il a par défaut 65536 octets dans mon environnement
momose@momose-pc:~$ java -version
openjdk version "11.0.3" 2019-04-16
OpenJDK Runtime Environment (build 11.0.3+7-Ubuntu-1ubuntu219.04.1)
OpenJDK 64-Bit Server VM (build 11.0.3+7-Ubuntu-1ubuntu219.04.1, mixed mode, sharing)
momose@momose-pc:~$
Que se passe-t-il si je spécifie un grand ReservedCodeCacheSize de 1 Go pour ne pas avoir à me soucier de manquer de cache de code? La JVM réserve la zone de mémoire native pour 1 Go. Cependant, il ne sera attribué qu'une fois utilisé.
↓ Matériaux de référence [\ [tips ] \ [Java ] Comment vérifier l'utilisation de la zone CodeCache \ -Akira's Tech Notes](http://luozengbin.github.io/blog/2015-09-01-%5Btips%5D%5Bjava% 5Dcodecache% E9% A0% 98% E5% 9F% 9F% E4% BD% BF% E7% 94% A8% E7% 8A% B6% E6% B3% 81% E3% 81% AE% E7% A2% BA% E8% AA% 8D% E6% 96% B9% E6% B3% 95.html)
Vous pouvez utiliser jconsole pour surveiller la taille de votre cache de code. Si vous sélectionnez Cache de code de pool de mémoire dans le panneau Mémoire, un graphique s'affiche (jusqu'à Java 8).
On dit que Java 9 ou version ultérieure sera géré dans une zone appelée Code Heap. ↓ Onglet Mémoire dans jconsole
Selon -XX: + SegmentedCodeCache
de ceci (https://docs.oracle.com/javase/jp/9/tools/java.htm),
Il semble que le traitement en divisant le segment empêche la fragmentation du code et améliore l'efficacité.
Selon Java9 \ (\ based on Oracle JVM) catchup \ -Qiita, il est divisé en 3 segments selon le type de code.
Le nombre d'exécutions affecte le moment de la compilation.
Il n'y a qu'un seul cas où vous devez ajuster le seuil de compilation. Si la somme des deux valeurs suivantes dépasse le seuil, elle sera mise en file d'attente pour le compilateur. Cela s'appelle "compilation standard" (pas le nom officiel).
--Compteur d'appels: nombre d'appels de méthode
Avec une compilation standard, un traitement long en une boucle, une méthode longue, ce n'est pas bien optimisé, Si le compteur de front arrière dépasse le seuil, seule la boucle sera compilée. Cette compilation s'appelle "OSR (remplacement sur pile)".
Le seuil de compilation standard peut être spécifié avec l'indicateur -XX: CompileThreshold = N
.
La valeur par défaut est 1500 pour le compilateur client et 10000 pour le compilateur serveur.
Condition de seuil de compilation OSR
Valeur du compteur de bord arrière> CompileThreshold(OnStackReplacePercentage
- InterpreterProfilePercentage) / 100
---XX: InterpreterProfilePercentage = N
vaut 33 par défaut
---XX: OnStackReplacePercentage = N
vaut par défaut 933 pour le compilateur client et 140 pour le compilateur serveur
Le seuil pour le compilateur client est donc
1500 * (933 - 33) / 100 = 13500
Ainsi, dans le cas du compilateur serveur, le seuil est
10000 * (140 - 33) / 100 = 10700
Sera
Chaque fois que la JVM atteint un point de sécurité, la valeur de chaque compteur est décrémentée. Par conséquent, toutes les méthodes ne seront pas compilées à un moment donné. Il existe donc des méthodes "gluantes" qui s'exécutent assez souvent, mais qui ne se compilent pas (pas à chaud) (ce qui est aussi l'une des raisons pour lesquelles la compilation hiérarchique est si rapide).
L'indicateur -XX: + PrintCompilation
(faux par défaut).
Chaque fois que vous compilez
Attribut ID de compilation d'horodatage(Niveau de compilation hiérarchique)Taille du nom de la méthode désoptimisée
Un journal avec le contenu tel que s'affiche.
L'attribut est
--%
: compilation OSR
--s
: méthode synchronisée
--!
: La méthode a des lancers
-- b
: Compiler en mode bloquant (pas de sortie dans Java actuel)
--n
: wrapper de méthode native généré par le compilateur
La taille est la taille du code d'octet Java S'il est désoptimisé, vous recevrez un message indiquant qu'il a été désoptimisé.
Vous pouvez également obtenir des informations sur la compilation de programmes Java déjà en cours d'exécution.
jstat -compiler ${ID de processus}
Vous pouvez également afficher la dernière version compilée toutes les 1000 millisecondes.
jstat -printcompilation ${ID de processus} 1000
La compilation OSR prend souvent du temps.
Contenu maniaque, semble être pour les ingénieurs JVM. Il est peu probable que vous implémentiez le contenu de réglage décrit ici ...
La compilation s'exécute de manière asynchrone et le nombre de threads du compilateur change en fonction du nombre de processeurs et du type de compilateur.
Le nombre de threads peut être modifié avec -XX: CICompilerCount = N
.
Dans le cas de la compilation hiérarchique, 1/3 est la compilation du client et le reste est la compilation du serveur.
Spécifier -XX: + BackgroundCompilation
empêchera la compilation d'être asynchrone.
Le code accessible via Getter / Setter est intégré par des compilateurs modernes.
L'intégration est activée par défaut.
Il peut également être désactivé avec -XX: -Inline
.
Les conditions pour l'inlining sont la chaleur et la taille du bytecode.
S'il fait chaud et que la taille du code d'octet est de 325 octets (modifiable avec -XX: MaxFreqInlineSize = N
) ou moins, l'inlining est effectué.
Si la taille est de 35 octets ou moins (modifiable avec -XX: MaxInlineSize = N
), l'inlining est effectué sans condition.
Optimisation lorsque -XX: + DoEscapeAnalysis
(la valeur par défaut est true
) est activé.
Il semble faire diverses choses, mais par exemple
public class Factorial {
private BigInteger factorial;
private int n;
public Factorial(int n) {
this.n = n;
}
public synchronized BigInteger getFactorial() {
if (factorial == null)
factorial = ...;
return factorial;
}
}
Contre
ArrayList<BigInteger> list = new ArrayList<BigInteger>();
for (int i = 0; i < 100; i++) {
Factorial factorial = new Factorial(i);
list.add(factorial.getFactorial());
}
Une optimisation avancée est effectuée (dans de rares cas, il peut y avoir un bug)
La non-optimisation n'est plus un entrant, elle se transforme en zombie et le GC tourne
Il y en a deux, et la première consiste à effectuer une optimisation en s'associant à la classe d'implémentation pour une interface spécifique, mais si cette prémisse est cassée, elle n'est pas optimisée. La seconde est l'implémentation de la compilation hiérarchique, qui est désoptimisée en la marquant comme n'étant pas un entrant une fois la compilation par le compilateur serveur terminée.
Si made zombie apparaît dans le journal de compilation, le code non-participant sera abandonné et le GC fonctionnera.
↓ Matériaux de référence Java-JA13-Architect-evans.pdf
--0: Code exécuté par l'interpréteur --1: Code compilé par le compilateur client en mode simple --2: Code compilé par le compilateur client en mode restreint ―― 3: Code compilé par le compilateur client en mode complet --4: Code compilé par le compilateur serveur
Il fonctionne au niveau 0 dans l'état initial, et dans la plupart des cas, il semble être compilé au niveau 3 d'abord, puis au niveau 4. On dit que les niveaux 1 et 2 sont utilisés lorsque la file d'attente du compilateur est pleine (elle se compile à grande vitesse car elle n'utilise pas de profileur). Naturellement, le code non optimisé remonte au niveau 0.
--La compilation hiérarchique la plus solide
De plus, le modificateur final semble ** n'avoir aucun effet sur les performances **.
Recommended Posts