Depuis la fin des années 1990, la vitesse d'exécution des programmes Java s'est considérablement améliorée grâce à l'introduction de la compilation à la volée (JIT) (en 1997 pour Java 1.1 ) à l'ajout de fonctionnalités du langage favorisant une meilleure analyse du code et aux optimisations de la JVM (comme l'adoption de HotSpot par défaut pour la JVM de Sun en 2000). Les stratégies sophistiquées de gestion de la mémoire ont également contribué à ces améliorations. L'exécution matérielle du bytecode Java, telle que celle proposée par Jazelle d'ARM , a été explorée mais non déployée.
Les performances d'un programme Java compilé en bytecode dépendent de la manière dont ses tâches sont gérées de façon optimale par la machine virtuelle Java (JVM) hôte, et de la façon dont cette dernière exploite les fonctionnalités du matériel et du système d'exploitation . Par conséquent, tout test ou comparaison de performances Java doit impérativement mentionner la version, le fournisseur, le système d'exploitation et l'architecture matérielle de la JVM utilisée. De même, les performances du programme équivalent compilé nativement dépendent de la qualité du code machine généré ; le test ou la comparaison doit donc également indiquer le nom, la version et le fournisseur du compilateur utilisé, ainsi que ses directives d'optimisation activées .
machine virtuelle à les implémenter avec succès, elles ont également été fréquemment utilisées sur d'autres plateformes similaires.Compilation juste à temps
Optimisation adaptative
Une machine virtuelle Java comme HotSpot peut également désoptimiser du code précédemment compilé à la volée (JIT). Cela permet d'effectuer des optimisations agressives (et potentiellement dangereuses), tout en conservant la possibilité de désoptimiser ultérieurement le code et de revenir à une exécution plus sûre.
Collecte des ordures
Autres méthodes d'optimisation
Oups compressé
La compression des objets permet à Java 5.0 et versions ultérieures d'adresser jusqu'à 32 Go de mémoire (tas) avec des références 32 bits. Java ne prend pas en charge l'accès aux octets individuels, mais uniquement aux objets, alignés par défaut sur 8 octets. De ce fait, les 3 bits de poids faible d'une référence de tas sont toujours à 0. En réduisant la résolution des références 32 bits à des blocs de 8 octets, l'espace adressable peut être étendu à 32 Go. Cela réduit considérablement la consommation de mémoire par rapport à l'utilisation de références 64 bits, car Java utilise les références beaucoup plus fréquemment que certains langages comme C++. Java 8 prend en charge des alignements plus étendus, tels que l'alignement sur 16 octets, pour gérer jusqu'à 64 Go avec des références 32 bits.classe , la JVM de Sun vérifie son bytecode Java (voir vérificateur de bytecode ). Cette vérification est effectuée à la demande : le bytecode des classes n'est chargé et vérifié que lorsque la classe est chargée et préparée pour l'utilisation, et non au démarrage du programme. Cependant, comme les bibliothèques de classes Java sont elles-mêmes des classes Java classiques, elles doivent également être chargées lors de leur utilisation, ce qui explique que le temps de démarrage d'un programme Java soit souvent plus long que celui d' un programme C++ , par exemple.
Une méthode appelée vérification en temps fractionné , introduite pour la première fois dans la plateforme Java, Micro Edition (J2ME), est utilisée dans la JVM depuis la version 6 de Java . Elle divise la vérification du bytecode Java en deux phases :
- Au moment de la conception – lors de la compilation d'une classe du code source vers le bytecode
- Exécution – lors du chargement d'une classe.
En pratique, cette méthode consiste à exploiter les connaissances du compilateur Java sur le flux d'exécution des classes et à annoter le bytecode des méthodes compilées avec un résumé de ces informations. Cela ne simplifie pas sensiblement la vérification à l'exécution , mais permet certains raccourcis.le multithreading au niveau du langage. Le multithreading permet aux programmes d'exécuter plusieurs processus simultanément, améliorant ainsi les performances des applications fonctionnant sur des systèmes multiprocesseurs ou multicœurs. De plus, une application multithread peut rester réactive aux entrées, même lors de l'exécution de tâches de longue durée.
Cependant, les programmes utilisant le multithreading doivent veiller particulièrement à la gestion des objets partagés entre les threads, en verrouillant l'accès aux méthodes ou blocs partagés lorsqu'ils sont utilisés par l'un des threads. Le verrouillage d'un bloc ou d'un objet est une opération longue en raison de la nature de l' opération sous-jacente au niveau du système d'exploitation (voir contrôle de la concurrence et granularité des verrous ).
Comme la bibliothèque Java ne sait pas quelles méthodes seront utilisées par plusieurs threads, la bibliothèque standard verrouille toujours les blocs lorsque cela est nécessaire dans un environnement multithread.
Avant Java 6, la machine virtuelle verrouillait systématiquement les objets et les blocs à la demande du programme, même en l'absence de risque de modification simultanée d'un objet par deux threads différents. Par exemple, dans ce cas précis, une variable locale Me" ) ; v.add ( " You " ) ; v.add ( " Her " ); return v.toString ( ) ; }
À partir de Java 6, les blocs de code et les objets ne sont verrouillés que lorsque cela est nécessaire, donc dans le cas ci-dessus, la machine virtuelle ne verrouillerait pas du tout l'objet Vector.
Depuis la version 6u23, Java inclut la prise en charge de l'analyse d'échappement.
Améliorations de l'attribution des registres
Avant Java 6 , l'allocation des registres était très rudimentaire dans la machine virtuelle cliente (ils ne s'étendaient pas sur plusieurs blocs ), ce qui posait problème pour les processeurs disposant de moins de registres , comme les x86 . Si aucun registre n'était disponible pour une opération, le compilateur devait effectuer une copie des données entre les registres et la mémoire , ce qui prenait du temps (l'accès aux registres étant nettement plus rapide). En revanche, la machine virtuelle serveur utilisait un allocateur à graphes de couleurs et ne rencontrait pas ce problème.
Une optimisation de l'allocation des registres a été introduite dans le JDK 6 de Sun ; il est alors devenu possible d'utiliser les mêmes registres entre les blocs (le cas échéant), réduisant ainsi les accès à la mémoire. Ceci a permis d'obtenir un gain de performance d'environ 60 % dans certains benchmarks.
Partage des données de classe
Le partage de données de classes (appelé CDS par Sun) est un mécanisme qui réduit le temps de démarrage des applications Java et leur empreinte mémoire . Lors de l'installation du JRE , le programme d'installation charge un ensemble de classes depuis le fichier JAR système (le fichier JAR contenant toute la bibliothèque de classes Java, appelé rt.jar) dans une représentation interne privée, puis enregistre cette représentation dans un fichier appelé « archive partagée ». Lors des invocations JVM suivantes, cette archive partagée est mappée en mémoire , ce qui permet d'économiser le coût de chargement de ces classes et de partager une grande partie des métadonnées JVM de ces classes entre plusieurs processus JVM.
L’amélioration correspondante du temps de démarrage est plus évidente pour les petits programmes.
Historique des améliorations de performance
JDK 1.1.6 : Première compilation juste-à-temps ( compilateur JIT de Symantec )
J2SE 1.2 : Utilisation d'un collecteur générationnel .
J2SE 1.3 : Compilation juste-à-temps par HotSpot .
J2SE 1.4 : Voir ici pour un aperçu de Sun des améliorations de performances entre les versions 1.3 et 1.4.
Java SE 5.0 : Partage de données de classe
Java SE 6 :
- vérification du bytecode fractionné
- Analyse des issues de secours et grossissement des serrures
- Améliorations de l'attribution des registres
Autres améliorations :
- Améliorations de la vitesse du pipeline Java OpenGL Java 2D
- Les performances de Java 2D se sont également améliorées de manière significative dans Java 6
Voir aussi « Aperçu de Sun sur les améliorations de performances entre Java 5 et Java 6 ».
Java SE 6 Mise à jour 10
- Java Quick Starter réduit le temps de démarrage de l'application en préchargeant une partie des données JRE au démarrage du système d'exploitation sur le cache disque .
- Les composants de la plateforme nécessaires à l'exécution d'une application web accessible sans JRE sont désormais téléchargés en premier. Le JRE complet pèse 12 Mo ; une application Swing classique ne nécessite que 4 Mo de téléchargement pour démarrer. Les autres composants sont ensuite téléchargés en arrière-plan.
- Les performances graphiques sous Windows ont été améliorées grâce à l'utilisation intensive de Direct3D par défaut, et à l'utilisation de shaders sur l'unité de traitement graphique (GPU) pour accélérer les opérations Java 2D complexes .
Java 7
Plusieurs améliorations de performances ont été publiées pour Java 7 : d’autres améliorations de performances sont prévues pour une mise à jour de Java 6 ou Java 7 :
- Fournir une prise en charge JVM pour les langages de programmation dynamiques , suivant le travail de prototypage actuellement réalisé sur la machine Da Vinci (machine virtuelle multilingue),
- Améliorer la bibliothèque de concurrence existante en gérant le calcul parallèle sur des processeurs multicœurs ,
- Autoriser la JVM à utiliser à la fois les compilateurs JIT client et serveur dans la même session avec une méthode appelée compilation hiérarchisée :
- Le client serait utilisé au démarrage (car il est performant au démarrage et pour les petites applications),
- Le serveur serait utilisé pour l'exécution à long terme de l'application (car il est plus performant que le compilateur client pour cela).
- Remplacez le collecteur de déchets concurrent à faible pause existant (également appelé collecteur concurrent de marquage-balayage (CMS)) par un nouveau collecteur appelé Garbage First (G1) afin de garantir des pauses cohérentes dans le temps.
Comparaison avec d'autres langues
Comparer objectivement les performances d'un programme Java et d'un programme équivalent écrit dans un autre langage, tel que C++, nécessite un banc d'essai rigoureux et bien conçu, comparant des programmes effectuant des tâches identiques. La plateforme cible du compilateur de bytecode Java est la plateforme Java elle-même, et le bytecode est interprété ou compilé en code machine par la JVM. Les autres compilateurs ciblent presque toujours une plateforme matérielle et logicielle spécifique, produisant un code machine qui reste quasiment inchangé lors de l'exécution . Ces deux approches distinctes engendrent des scénarios très différents et difficiles à comparer : compilations et recompilations statiques ou dynamiques , disponibilité d'informations précises sur l'environnement d'exécution, etc.
Java est souvent compilé à la volée lors de l'exécution par la machine virtuelle Java , mais peut également être compilé à l'avance , comme C++. Lorsqu'il est compilé à la volée, les micro-benchmarks du jeu The Computer Language Benchmarks indiquent ce qui suit concernant ses performances :
- plus lents que les langages compilés tels que C ou C++ ,
- similaire à d'autres langages compilés juste-à-temps tels que C# ,
- beaucoup plus rapide que les langages sans compilateur de code natif efficace ( JIT ou AOT ), tels que Perl , Ruby , PHP et Python .
Vitesse du programme
Les benchmarks mesurent souvent les performances de petits programmes à forte intensité de calcul. Dans certains cas rares de programmes réels, Java surpasse C. On peut citer l'exemple du benchmark de Jake2 (un clone de Quake II écrit en Java par traduction du code C original sous licence GPL ). La version Java 5.0 est plus performante que son équivalent C sur certaines configurations matérielles. Bien que la méthode de mesure ne soit pas précisée (par exemple, si l'exécutable original de Quake II compilé en 1997 a été utilisé, ce qui pourrait être problématique car les compilateurs C actuels offrent probablement de meilleures optimisations pour Quake), l'étude montre comment le même code source Java peut bénéficier d'un gain de vitesse considérable grâce à une simple mise à jour de la machine virtuelle, un gain impossible à obtenir avec une approche 100 % statique.
Pour d'autres programmes, l'équivalent C++ peut s'exécuter, et s'exécute généralement, nettement plus rapidement que son équivalent Java. Un test de performance réalisé par Google en 2011 a révélé un facteur 10 entre C++ et Java. À l'inverse, un test de performance académique effectué en 2012 avec un algorithme de modélisation 3D a montré que la JVM Java 6 était de 1,09 à 1,91 fois plus lente que C++ sous Windows.
Certaines optimisations possibles en Java et dans des langages similaires peuvent ne pas être possibles dans certaines circonstances en C++ :
- L'utilisation de pointeurs de style C peut entraver l'optimisation dans les langages qui prennent en charge les pointeurs.
- L'utilisation des méthodes d'analyse d'échappement est limitée en C++ , par exemple parce qu'un compilateur C++ ne sait pas toujours si un objet sera modifié dans un bloc de code donné en raison des pointeurs ,
- En raison de la consultation supplémentaire de la table virtuelle effectuée par C++, les méthodes d'instance dérivées sont plus rapides en Java qu'en C++ d'accéder aux méthodes virtuelles dérivées. Cependant, les méthodes non virtuelles en C++ ne souffrent pas de ce goulot d'étranglement et présentent donc des performances similaires à celles de Java.
La JVM est également capable d'effectuer des optimisations spécifiques au processeur ou une expansion en ligne . De plus, sa capacité à désoptimiser du code déjà compilé ou intégré lui permet parfois d'effectuer des optimisations plus poussées que celles réalisées par les langages statiquement typés lorsque des fonctions de bibliothèques externes sont impliquées.
Les résultats des microbenchmarks entre Java et C++ dépendent fortement des opérations comparées. Par exemple, lors d'une comparaison avec Java 5.0 :
- Les opérations arithmétiques 32 et 64 bits, les entrées/sorties de fichiers , et la gestion des exceptions présentent des performances similaires à celles des programmes C++ comparables.
- Les opérations sur les tableaux ont de meilleures performances en C.
- Les performances des fonctions trigonométriques sont bien meilleures en C.
- Notes
En comparaison avec des environnements d'exécution populaires similaires, pour les petits programmes exécutés sur une machine Windows, le temps de démarrage semble être similaire à celui de Mono et un peu plus lent que celui de .NET .
Il semble que la majeure partie du temps de démarrage soit due aux opérations d'entrée/sortie plutôt qu'à l'initialisation de la JVM ou au chargement des classes (le fichier de données de classe rt.jar pèse à lui seul 40 Mo et la JVM doit y rechercher de nombreuses données). Certains tests ont montré que, bien que la nouvelle méthode de vérification du bytecode fractionné améliore le chargement des classes d'environ 40 %, elle ne permet qu'une amélioration d'environ 5 % du temps de démarrage pour les programmes volumineux.
Bien qu'il s'agisse d'une petite amélioration, elle est plus visible dans les petits programmes qui effectuent une opération simple puis se terminent, car le chargement des données de la plateforme Java peut représenter plusieurs fois la charge de l'opération du programme lui-même.
À partir de Java SE 6 Update 10, le JRE de Sun est livré avec un Quick Starter qui précharge les données de classe au démarrage du système d'exploitation pour obtenir les données à partir du cache disque plutôt que du disque.
Excelsior JET aborde le problème sous un angle différent. Son optimiseur de démarrage réduit la quantité de données à lire sur le disque au démarrage de l'application et rend ces lectures plus séquentielles.
En novembre 2004, les scripts d'utiliser une JVM comme un démon , afin d'exécuter une ou plusieurs applications Java sans la surcharge liée au démarrage de la JVM. Le démon Nailgun est vulnérable : « tous les programmes sont exécutés avec les mêmes permissions que le serveur ». Lorsque la sécurité multi-utilisateurs est requise, Nailgun est inadapté sans précautions particulières. Les scripts où le démarrage de la JVM par application représente la majeure partie de l'utilisation des ressources bénéficient d' une amélioration des performances d'exécution d'un à deux ordres de grandeur
Utilisation de la mémoire
L'utilisation de la mémoire en Java est bien supérieure à celle en C++ car :
- En Java , chaque objet et chaque tableau consomment 8 octets et 12 octets respectivement . Si la taille d'un objet n'est pas un multiple de 8 octets, elle est arrondie au multiple de 8 supérieur. Ainsi, un objet contenant un champ d'un octet occupe 16 octets et nécessite une référence de 4 octets. En C++, un pointeur (généralement de 4 ou 8 octets) est également alloué pour chaque objet dont la classe déclare, directement ou indirectement, des fonctions virtuelles .
- L'absence d'arithmétique d'adresses rend actuellement impossible la création de conteneurs économes en mémoire, tels que des structures à espacement réduit et des listes chaînées XOR ( le projet OpenJDK Valhalla vise à atténuer ces problèmes, bien qu'il ne vise pas à introduire l'arithmétique des pointeurs ; cela ne peut pas être fait dans un environnement avec ramasse-miettes).
- Contrairement à malloc et new, la surcharge de performance moyenne du ramasse-miettes tend asymptotiquement vers zéro (plus précisément, un cycle CPU) lorsque la taille du tas augmente. bibliothèque de classes Java doivent être chargées avant l'exécution du programme (au moins les classes utilisées au sein du programme). Cela entraîne une surcharge mémoire importante pour les petites applications.x87 à virgule flottante, Java, depuis la version 1.4, effectue une réduction logicielle des arguments pour les fonctions sinus et cosinus, ce qui entraîne une forte baisse de performances pour les valeurs hors de l'intervalle. interface JNI (Java Native Interface ) induit une surcharge importante, rendant coûteux le passage entre le code exécuté sur la JVM et le code natif. L'accès natif Java (JNA) offre aux programmes Java un accès simplifié aux bibliothèques partagées natives ( bibliothèques de liens dynamiques (DLL) sous Windows) via le seul code Java, sans JNI ni code natif. Cette fonctionnalité est comparable à Platform/Invoke sous Windows et à ctypes en Python . L'accès est dynamique à l'exécution, sans génération de code. Cependant, cette approche a un coût : JNA est généralement plus lent que JNI.
interface utilisateur
Swing a longtemps été perçu comme plus lent que les bibliothèques de widgets natives , car il délègue le rendu des widgets à l' API 2D Java . Cependant, les tests comparatifs de performances entre Swing et le Standard Widget Toolkit (SWTK) , qui délègue le rendu aux bibliothèques d'interface graphique natives du système d'exploitation, ne mettent pas en évidence de solution nettement supérieure, les résultats dépendant fortement du contexte et de l'environnement. De plus, le framework JavaFX , plus récent et destiné à remplacer Swing, corrige nombre de ses problèmes inhérents.
Utilisé pour le calcul haute performance
Certains estiment que les performances de Java pour le calcul haute performance (HPC) sont similaires à celles de Fortran sur les benchmarks nécessitant une puissance de calcul importante, mais que les JVM présentent encore des problèmes d'évolutivité pour effectuer des communications intensives sur un réseau de calcul distribué .
Cependant, des applications de calcul haute performance écrites en Java ont remporté des compétitions de benchmark. En 2008 et 2009 , un cluster basé sur Apache Hadoop (un projet de calcul haute performance open source écrit en Java) a été capable de trier un téraoctet et un pétaoctet d'entiers le plus rapidement. La configuration matérielle des systèmes concurrents n'était cependant pas fixe
Dans les concours de programmation
Les programmes en Java démarrent plus lentement que ceux dans d'autres langages compilés. Ainsi, certains systèmes de jugement en ligne, notamment ceux hébergés par les universités chinoises, utilisent des limites de temps plus longues pour les programmes Java afin d'être équitables envers les concurrents utilisant Java.