Tas et optimisation de la mémoire
Aujourd'hui, j'ai testé la fonction de tri automatique des données d'un projet, triant des dizaines de milliers d'enregistrements et d'images dans la base de données. Lorsque l'opération était proche de la fin, java.lang.outofMemoryError, une erreur dans l'espace de tas de Java a été révélée. Dans le passé, j'ai rarement rencontré de telles erreurs de mémoire dans les programmes d'écriture, car Java a un mécanisme de collecteur de déchets, donc je n'y ai pas accordé beaucoup d'attention. Aujourd'hui, j'ai recherché des informations en ligne et je l'ai réglé sur cette base.
1. Empiler et empiler
Built en tas avec un nouveau collecteur de déchets est responsable du recyclage
1. Lorsque le programme commence à s'exécuter, le JVM obtient de la mémoire du système d'exploitation, dont une partie est la mémoire du tas. La mémoire du tas est généralement disposée vers le haut au bas de l'adresse de stockage.
2. Le tas est une zone de données "d'exécution", et l'objet instancié dans la classe alloue l'espace à partir du tas;
3. L'allocation de l'espace sur le tas est établie à travers des instructions telles que "Nouveau". Le tas est la taille de la mémoire allouée dynamiquement, et la durée de vie n'a pas besoin d'être racontée au compilateur à l'avance;
4. Contrairement à C ++, Java gère automatiquement le tas et la pile, et le collecteur des ordures peut recycler automatiquement la mémoire du tas qui n'est plus utilisée;
5. L'inconvénient est que comme la mémoire est allouée dynamiquement au moment de l'exécution, la vitesse d'accès à la mémoire est plus lente.
Stack - Stockez les types de base et les types de référence, rapidement
1. La structure de données de premier, puis de sortie, est généralement utilisée pour enregistrer les paramètres et les variables locales dans la méthode;
2. En Java, toutes les variables de types de base (courte, int, long, octet, flotteur, double, booléen, char) et les types de référence sont stockés dans la pile;
3. L'espace de vie des données dans la pile se trouve généralement dans les lunettes actuelles (la zone entourée de {...};
4. La vitesse d'accès de la pile est plus rapide que le tas, juste derrière les registres directement situés dans le CPU;
5. Les données de la pile peuvent être partagées et plusieurs références peuvent pointer vers la même adresse;
6. L'inconvénient est que la taille des données et la durée de vie de la pile doivent être déterminées et manquer de flexibilité.
2. Paramètres de mémoire
1. Vérifiez l'état de la mémoire de la machine virtuelle
long maxControl = runtime.getRuntime (). MaxMemory (); // Obtenez la quantité maximale de mémoire que la machine virtuelle peut contrôler CurrentUse = runtime.getRuntime (). TotalMemory (); // Obtenez la quantité de mémoire actuellement utilisée par la machine virtuelle
Par défaut, maxControl = 66650112b = 63,5625m de la machine virtuelle Java;
Si vous ne faites rien, le courant utilise mesuré sur ma machine = 5177344b = 4,9375m;
2. Commande pour définir la taille de la mémoire
-XMS <Size> Définissez la taille du tas Java initial: définissez la taille de la mémoire du tas d'initialisation JVM; Cette valeur peut être définie de la même chose que -xmx pour éviter la mémoire de redistribution JVM chaque fois que la collection de déchets est terminée.
-XMX <Size> Définissez la taille maximale du tas Java: définissez la taille maximale de la mémoire du tas du JVM;
-Xmn <Size>: Réglez la taille de la jeune génération, la taille entière du tas = la taille de la jeune génération + la taille de l'ancienne génération + la taille de la dernière génération.
-XSS <Size> Définissez la taille de la pile de threads Java: Définissez la taille de la mémoire de la pile de threads JVM;
3. Opérations spécifiques (1) Paramètres de mémoire JVM:
Ouvrez MyEclipse (Eclipse) Préférences de fenêtre-java-installé JRES-Edit-Default VM Arguments
ENTER: -XMX128M -XMS64M -XMN32M -XSS16M
(2) Paramètres de mémoire IDE:
Modifiez la configuration sous -vMargs dans myECLipse.ini (ou eclipse.ini dans le répertoire racine Eclipse):
(3) Paramètres de mémoire Tomcat
Ouvrez le dossier bin dans le répertoire racine de Tomcat et modifiez Catalina.bat
Modifier à: Définir Java_OPTS = -XMS256M -XMX512M
3. Analyse d'erreur OutOfMemoryError dans Java Heap
Lorsque le JVM est démarré, la mémoire du tas définie par le paramètre -xms est utilisée. Au fur et à mesure que le programme continue et crée plus d'objets, le JVM commence à étendre la mémoire du tas pour contenir plus d'objets. Le JVM utilise également un collecteur de déchets pour recycler la mémoire. Lorsque la mémoire de tas maximale définie par -xmx est presque atteinte, si plus de mémoire ne peut être allouée au nouvel objet, le JVM lancera java.lang.outofMemoryError et le programme se bloquera. Avant de lancer OutofMemoryError, le JVM essaiera de libérer suffisamment d'espace avec le collecteur des ordures, mais lancera cette erreur lorsqu'il constatera qu'il n'y a toujours pas assez d'espace. Pour résoudre ce problème, vous devez être clair sur les informations sur les objets du programme, tels que les objets créés, quels objets occupent la quantité d'espace, etc. Vous pouvez utiliser un profileur ou un analyseur de tas pour gérer les erreurs d'OutofMemoryError. "Java.lang.outofMemoryError: Java Heap Space" signifie que le tas n'a pas assez d'espace et ne peut pas continuer à se développer. "java.lang.outofMemoryError: Permgen Space" signifie que la génération permanente est pleine et que votre programme ne peut plus charger la classe ou allouer une chaîne.
4. Collection de tas et d'ordures
Nous savons que les objets sont créés dans la mémoire du tas, la collecte des ordures est un processus qui efface les objets morts de l'espace du tas et renvoie ces mémoire au tas. Afin d'utiliser le collecteur des ordures, le tas est principalement divisé en trois zones, à savoir l'espace de génération à l'ancienne ou de génération d'ancienne ou de permanence. La nouvelle génération est un espace utilisé pour stocker des objets nouvellement créés et est utilisé lorsque l'objet est nouvellement créé. S'ils sont utilisés pendant longtemps, ils seront transférés à l'ancienne génération (ou à la génération titulaire) par le collecteur des ordures. L'espace PERM est l'endroit où le JVM stocke les méta-données, tels que les classes, les méthodes, les pools de chaînes et les détails au niveau de la classe.
5. Résumé:
1. La mémoire de tas Java fait partie de la mémoire allouée au JVM par le système d'exploitation.
2. Lorsque nous créons des objets, ils sont stockés dans la mémoire de tas Java.
3. Pour faciliter la collecte des ordures, l'espace de tas Java est divisé en trois zones, appelée nouvelle génération, vieille génération ou génération titulaire et espace permanen.
4. Vous pouvez ajuster la taille de l'espace de tas Java en utilisant les options de ligne de commande JVM -XMS, -XMX et -XMN.
5. Vous pouvez utiliser jconsole ou runtime.maxMemory (), runtime.totalmory () et runtime.freememory () pour afficher la taille de la mémoire de tas en java.
6. Vous pouvez utiliser la commande "jmap" pour obtenir le vidage du tas et utiliser "Jhat" pour analyser le vidage du tas.
7. L'espace de tas Java est différent de l'espace de pile. L'espace de pile est utilisé pour stocker des piles d'appels et des variables locales.
8. Le collecteur de déchets Java est utilisé pour récupérer la mémoire occupée par des objets morts (objets qui ne sont plus utilisés) et le libérer dans l'espace de tas Java.
9. Lorsque vous rencontrez Java.lang.outofMemoryError, vous n'avez pas à vous inquiéter. Parfois, il vous suffit d'augmenter l'espace de tas. Mais si cela se produit fréquemment, vous devez voir s'il y a une fuite de mémoire dans le programme Java.
10. Utilisez des outils d'analyse de vidage du profileur et du tas pour afficher l'espace de tas Java, et vous pouvez voir combien de mémoire est allouée à chaque objet.
Explication détaillée du stockage de pile
Java Stack Storage a les caractéristiques suivantes:
1. La taille des données et le cycle de vie de la pile doivent être déterminés.
Par exemple, le stockage du type de base: int a = 1; Cette variable contient une valeur littérale, A est une référence au type int, pointant la valeur littérale de 3. En raison de la taille et de la durée de vie de ces données littérales, ces valeurs littérales sont définies de manière correcte dans un bloc de programme, et après la sortie du bloc du programme, les valeurs littérales disparaissent), existent dans la pile pour le bien de la vitesse.
2. Les données existantes dans la pile peuvent être partagées.
(1) Stockage de données de type de base:
comme:
int a = 3; int b = 3;
Le compilateur traite d'abord int a = 3; Il créera d'abord une référence à la variable A dans la pile, puis découvrira s'il y a une adresse avec une valeur littérale de 3. Si elle n'est pas trouvée, elle ouvrira une adresse avec la valeur littérale de 3, puis pointer A vers l'adresse de 3. Processus Int B = 3; Après avoir créé la variable de référence de B, car il y a déjà une valeur littérale de 3 dans la pile, B est directement pointé vers l'adresse de 3. De cette façon, A et B pointent les deux à 3 en même temps.
Remarque: Cette référence littérale est différente de celle des objets de classe. En supposant que les références de deux objets de classe pointent vers un objet en même temps, si une variable de référence d'objet modifie l'état interne de l'objet, l'autre variable de référence d'objet reflète immédiatement ce changement. Au lieu de cela, la modification de sa valeur par une référence littérale ne fera pas modifier une autre valeur en conséquence. Comme dans l'exemple ci-dessus, après avoir défini les valeurs de A et B, que A = 4; Ensuite, B ne sera pas égal à 4, ou égal à 3. À l'intérieur du compilateur, lorsque A = 4 est rencontré, il recouvrera s'il y a une valeur littérale de 4 dans la pile. Sinon, rouvrez l'adresse pour stocker la valeur de 4; S'il existe déjà, pointez directement A vers cette adresse. Par conséquent, la variation de la valeur A n'affectera pas la valeur b.
(2) Stockage de données d'emballage:
Classes qui enveloppent les types de données de base correspondants, tels que entier, double, chaîne, etc. Toutes ces données de classe existent dans le tas. Java utilise la nouvelle instruction () pour afficher le compilateur et ne crée que dynamiquement au besoin au moment de l'exécution, il est donc plus flexible, mais l'inconvénient est qu'il prend plus de temps.
Par exemple: prenez la chaîne comme exemple.
La chaîne est une données d'emballage spéciales. Autrement dit, il peut être créé sous la forme de String str = new String ("ABC"); ou il peut être créé sous la forme de chaîne str = "ABC";. Le premier est le processus de création de classe standardisé, c'est-à-dire en Java, tout est un objet, et un objet est une instance de la classe, tous créés sous la forme de new (). Certaines classes de Java, comme la classe DateFormat, peuvent renvoyer une classe nouvellement créée via la méthode GetInstance () de la classe, qui semble violer ce principe. En fait, ce n'est pas le cas. Cette classe utilise le modèle Singleton pour renvoyer une instance de la classe, mais cette instance est créée à l'intérieur de la classe via new (), qui cache ce détail de l'extérieur.
Alors pourquoi l'instance n'est-elle pas créée via new () dans string str = "ABC";? Est-il violé le principe ci-dessus? En fait, il n'y en a pas.
À propos du travail interne de String str = "ABC". Java convertit en interne cette déclaration en étapes suivantes:
un. Définissez d'abord une variable de référence d'objet nommée STR à la classe String: String Str;
né Découvrez s'il y a une adresse avec la valeur "ABC" dans la pile. Sinon, ouvrez une adresse avec la valeur littérale "ABC", puis créez un nouvel objet O de la classe de chaîne et pointez la valeur de chaîne de O à cette adresse, et notez l'objet de référence O à côté de cette adresse dans la pile. S'il y a une adresse avec la valeur "ABC", recherchez l'objet O et renvoyez l'adresse d'O.
c. Point Str à l'adresse de l'objet O.
Il convient de noter que les valeurs de chaîne dans la classe de chaîne sont généralement stockées directement. Mais dans des situations comme String str = "ABC";, sa valeur de chaîne contient une référence aux données présentes dans la pile (c'est-à-dire: String str = "ABC"; à la fois le stockage de pile et le stockage de tas).
Pour mieux illustrer ce problème, nous pouvons le vérifier à travers les codes suivants.
String str1 = "ABC"; String str2 = "ABC"; System.out.println (str1 == str2); //vrai
(La valeur de vérité n'est renvoyée que si les deux références pointent vers le même objet. STR1 et STR2 pointant vers le même objet)
Le résultat montre que le JVM a créé deux références STR1 et STR2, mais un seul objet a été créé, et les deux références ont indiqué cet objet.
String str1 = "ABC"; String str2 = "ABC"; str1 = "BCD"; System.out.println (str1 + "," + str2); // BCD, ABC System.out.println (str1 == str2); //FAUX
Cela signifie que le changement d'attribution entraîne le changement de référence de l'objet de classe, STR1 pointe vers un autre nouvel objet, tandis que STR2 pointe toujours vers l'objet d'origine. Dans l'exemple ci-dessus, lorsque nous modifions la valeur de STR1 en "BCD", le JVM a constaté qu'il n'y a pas d'adresse pour stocker la valeur dans la pile, il a donc ouvert cette adresse et a créé un nouvel objet dont la valeur de chaîne pointe vers cette adresse.
En fait, la classe String est conçue pour être une classe immuable. Si vous souhaitez modifier sa valeur, vous le pouvez, mais le JVM crée tranquillement un nouvel objet basé sur la nouvelle valeur au moment de l'exécution (il ne peut pas être modifié en fonction de la mémoire d'origine), puis renvoie l'adresse de cet objet à la référence de la classe d'origine. Bien que ce processus de création soit complètement automatique, il prend plus de temps après tout. Dans un environnement plus sensible aux exigences temporelles, il aura certains effets néfastes.
String str1 = "ABC"; String str2 = "ABC"; str1 = "BCD"; String str3 = str1; System.out.println (STR3); // BCD String str4 = "bcd"; System.out.println (str1 == str4); //vrai
La référence à l'objet STR3 pointe directement vers l'objet pointé par STR1 (notez que STR3 ne crée pas un nouvel objet). Une fois que STR1 a changé sa valeur, créez une référence de chaîne STR4 et pointez un nouvel objet créé par STR1 Modification de la valeur. On peut constater que cette fois STR4 n'a pas créé un nouvel objet, réalisant ainsi le partage des données dans la pile.
String str1 = new String ("ABC"); String str2 = "ABC"; System.out.println (str1 == str2); //FAUXDeux références ont été créées. Deux objets ont été créés. Les deux références pointent vers deux objets différents.
String str1 = "ABC"; String str2 = new String ("ABC"); System.out.println (str1 == str2); //FAUXDeux références ont été créées. Deux objets ont été créés. Les deux références pointent vers deux objets différents.
Les deux codes ci-dessus indiquent que tant que l'objet est créé avec New (), il sera créé dans le tas et que ses chaînes sont stockées séparément. Même s'ils sont les mêmes que les données de la pile, ils ne seront pas partagés avec les données de la pile.
Résumer:
(1) Lorsque nous définissons une classe en utilisant un format tel que String str = "ABC";, nous tenons toujours pour acquis que nous avons créé un objet STR de la classe String. Inquiétez-vous pour le piège! L'objet n'a peut-être pas été créé! La seule chose qui est certaine est qu'une référence à la classe String est créée. Quant à savoir si cette référence pointe vers un nouvel objet, elle doit être considérée sur la base du contexte, sauf si vous utilisez la méthode nouvelle () pour créer explicitement un nouvel objet. Par conséquent, pour être plus précis, nous créons une variable de référence STR à un objet de la classe String, qui pointe vers une classe de chaîne avec une valeur de "ABC". En être conscient est utile pour dépanner des bugs difficiles dans les programmes.
(2) en utilisant une chaîne str = "ABC"; Peut améliorer la vitesse d'exécution du programme dans une certaine mesure, car le JVM décidera automatiquement s'il est nécessaire de créer un nouvel objet basé sur la situation réelle des données dans la pile. Pour le code de String str = new String ("ABC");, de nouveaux objets sont créés dans le tas, que leurs valeurs de chaîne soient égales ou non, s'il est nécessaire de créer de nouveaux objets, augmentant ainsi le fardeau du programme.
(3) En raison de la nature immuable de la classe String (car la valeur de la classe de wrapper ne peut pas être modifiée), lorsque la variable de chaîne doit être transformée fréquemment, la classe StringBuffer doit être considérée comme améliorant l'efficacité du programme.