Programmation simultanée (2) – Disposition de la mémoire d'objets Java et introduction aux verrous biaisés synchronisés, aux verrous légers et aux verrous lourds

1. Disposition de la mémoire des objets Java

1. Disposition de la mémoire objet

Un objet est disposé en bas de Java (la moitié droite est l'espace d'adressage continu du tableau), comme indiqué ci-dessous :
Insérer la description de l'image ici

Il y a trois parties au total :

1. En-tête de l'objet : stocke les métadonnées de l'objet, telles que le code de hachage, l'âge de génération du GC, l'indicateur d'état du verrouillage, les verrous détenus par les threads, etc.
2. Données d'instance : stocke le contenu réel des données de l'objet, c'est-à-dire différents types de variables définies par le programmeur.
3. Remplissez-le : Pour que la JVM accède plus rapidement aux données à l'intérieur de l'objet, un espace supplémentaire sera rempli derrière les données de l'instance afin que la taille de l'objet puisse être divisible par le système de gestion de mémoire de la machine virtuelle (généralement un multiple de 8) .

La taille de l'en-tête d'objet spécifique et la taille des données d'instance sont liées à l'implémentation spécifique de la machine virtuelle Java, au type d'objet, aux paramètres d'exécution de la machine virtuelle, etc., et ne sont généralement pas des valeurs fixes. Il convient de noter que la disposition de la mémoire des objets tableau est différente de celle des objets ordinaires. Les objets tableau stockeront en outre des informations sur la longueur du tableau.

1.1. En-tête d'objet

Cliquez pour consulter la documentation du site officiel du hotspot

(1) marquer le mot marquer le mot

L'en-tête de l'objet se compose de deux parties :

Insérer la description de l'image ici
Insérer la description de l'image ici

1. Marque d'objet (mot de marque) : stocke les métadonnées de l'objet, telles que le code de hachage, l'âge de génération du GC, l'indicateur d'état du verrou et le verrou détenu par le thread
2. Méta-informations de classe (pointeur de classe) : stocke le pointeur vers le object La première adresse où se trouvent les métadonnées de classe (klass).

Sur un système d'exploitation 64 bits, le Markword occupe 8 octets et le pointeur de type occupe 8 octets, pour un total de 16 octets. En d'autres termes, si vous créez simplement un nouvel objet, l'en-tête de l'objet occupera directement 16 octets (mais pas nécessairement, le pointeur de type peut être compressé).

Insérer la description de l'image ici

(2) pointeur de type pointeur de classe

Vous pouvez vous référer à la figure ci-dessous, le pointeur de type pointe vers la zone de méthode, par exemple, il existe une classe Customer, une nouvelle instance Customer, le pointeur de type de cette instance pointe vers les méta-informations de la classe Customer dans la zone de méthode.

Insérer la description de l'image ici

1.2. Données d'instance

Stocke les informations sur les données de champ de la classe , y compris les informations d'attribut de la classe parent ; s'il s'agit d'une partie d'instance de tableau, elle doit également inclure la longueur du tableau. Cette partie de la mémoire est alignée sur 4 octets.

Un exemple est le suivant :

public class MarkwordDemo {
    
    

    public static void main(String[] args) {
    
    
        new Apple();
    }
}
class Apple {
    
    
}

Directement nouvelle une instance Apple d'attribut vide, elle occupe déjà 16 octets en mémoire (indépendamment de la compression du pointeur de type). Et s'il y avait d'autres attributs dans la classe Apple ? Comme suit:

public class MarkwordDemo {
    
    

    public static void main(String[] args) {
    
    
        new Apple();
    }
}
class Apple {
    
    
    int size = 100;
    char a = 'a';
}

Un inttype occupe 4 octets, et un charcaractère occupe 1 octet, donc une nouvelle instance Apple occupera 16+5 = 21 octets, mais elle occupera finalement 24 octets, car pour faciliter la gestion de la mémoire en bas de Java, il lui faut à aligner et à compléter, et est généralement un multiple de 8, il fait donc 24 octets.

1.3. Remplissez-le

La machine virtuelle exige que l'adresse de départ de l'objet soit un multiple entier de 8 octets. Les données de remplissage ne doivent pas nécessairement exister. C'est juste pour l'alignement des octets. Cette partie de la mémoire est en outre alignée selon 8 octets.

2. Recherche sur la couche inférieure du verrouillage de synchronisation

markOop.hppIl y a le commentaire suivant dans le code source, comme indiqué ci-dessous :

Insérer la description de l'image ici

Après avoir simplifié les commentaires ci-dessus, nous obtenons le diagramme d'en-tête d'objet de machine virtuelle 64 bits, comme suit :

Insérer la description de l'image ici

Connaissant la structure interne de base de l'objet, examinons comment le verrouillage de synchronisation synchronisé précédent change dans l'en-tête de l'objet.

1. Disposition de la mémoire des objets Java View

Vous pouvez utiliser la classe d'outils Java jol pour vous aider à visualiser la création de la mise en page de new Object() en mémoire, comme indiqué ci-dessous :

1. Introduisez d’abord les dépendances

Il est recommandé d'utiliser la version 0.9 pour les packages dépendants . D'autres versions peuvent avoir des effets différents, alors soyez prudent.

		<dependency>
			<groupId>org.openjdk.jol</groupId>
			<artifactId>jol-core</artifactId>
			<version>0.9</version>
		</dependency>

2. Code démo

class MyObject {
    
    
}

public class ObjectMarkWordDemo {
    
    
    public static void main(String[] args) {
    
    
        System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
    }
}

Dirigez la nouvelle classe MyObject(), puis affichez la disposition de la mémoire via la classe d'outils ClassLayout. Les résultats de sortie sont les suivants :

Insérer la description de l'image ici

Insérer la description de l'image ici

Ajoutez deux types de variables à la classe MyObject comme suit :

class MyObject {
    
    
	int i = 25;
	boolean flag = false;
}

public class ObjectMarkWordDemo {
    
    
    public static void main(String[] args) {
    
    
        System.out.println(ClassLayout.parseInstance(new MyObject()).toPrintable());
    }
}

Ensuite, la disposition de la mémoire Java après la sortie est la suivante :

Insérer la description de l'image ici

Comme vous pouvez le voir ci-dessus, le pointeur de type devrait normalement occuper 8 octets, mais maintenant il en occupe 4. Nous pouvons utiliser la commande pour demander quelles commandes ont été exécutées au démarrage de la JVM :

java -XX:+PrintCommandLineFlags -version

Insérer la description de l'image ici

Comme le montre le +numéro ci-dessus, la JVM adopte par défaut la compression du pointeur de type.Peut économiser de l'espace mémoire, modifiez maintenant ce paramètre, comme indiqué ci-dessous :

-XX:-UseCompressedClassPointers

Insérer la description de l'image ici

Après l'avoir allumé, sous nouveau test, les résultats de sortie sont les suivants :

Insérer la description de l'image ici

Vous savez déjà comment afficher la disposition de la mémoire Java ci-dessus. Apprenons maintenant plus sur l'optimisation du verrouillage synchronisé et la mise à niveau du verrouillage.

2. Recherche sur les serrures synchronisées

Jetons un coup d'œil à la structure de la mémoire du mot de marque dans l'en-tête de l'objet mardkword, comme indiqué ci-dessous :

Insérer la description de l'image ici

arrière-plan d'optimisation du verrouillage synchronisé :

L’utilisation de verrous peut garantir la sécurité, mais elle peut également entraîner une dégradation des performances. Le mode sans verrouillage peut être basé sur des threads et améliorer les performances du programme, mais cela réduira la sécurité. Alors, comment pouvons-nous atteindre un équilibre ?

Par conséquent, la synchronisation a été adoptée depuis jdk1.5 锁升级pour améliorer les performances du programme et garantir sa sécurité.

Insérer la description de l'image ici

Avant jdk1.5, des verrous synchronisés et lourds du système d'exploitation étaient utilisés. Chaque fois que le verrou était verrouillé, un 用户态commutateur 内核态était nécessaire. Le commutateur était accompagné de nombreux processus de copie de données et les performances étaient très faibles.

Insérer la description de l'image ici

Les threads Java sont mappés aux threads natifs du système d'exploitation. Si vous souhaitez bloquer ou réactiver un thread, le système d'exploitation doit intervenir et vous devez basculer entre le mode utilisateur et le mode noyau. Cette commutation consommera beaucoup de temps système. ressources, car le mode utilisateur et l'état du noyau ont son propre espace mémoire dédié, des registres dédiés, etc. Le passage de l'état utilisateur à l'état du noyau nécessite de transmettre de nombreuses variables et paramètres au noyau. Le noyau doit également enregistrer certaines valeurs de registre, variables, etc. mappées par l'état de l'utilisateur pendant le changement, afin de faciliter. Une fois l'appel en mode noyau terminé, revenez en mode utilisateur et continuez à travailler.

Dans les premières versions de Java, la synchronisation est un verrou lourd et inefficace car le moniteur ( Monitor) repose sur Mutex Lockl'implémentation du système d'exploitation sous-jacent. La suspension et la reprise des threads doivent être transférées à l'état du noyau. Le blocage ou la réactivation d'un thread Java nécessite Le système d'exploitation. Cela se fait en changeant l'état du processeur. Ce changement d'état nécessite du temps CPU. Si le contenu du bloc de code est trop simple, le coût de ce changement est trop élevé.

Par exemple, si on ajoute le mot-clé synchronisé au bloc de code , le code est le suivant :

class MyObject {
    
    
    int a = 25;
    char b = 'b';
}
public class ObjectMarkWordDemo {
    
    
    public static void main(String[] args) {
    
    
        MyObject myObject = new MyObject();

        new Thread(()->{
    
    
            synchronized (myObject) {
    
    
                System.out.println(">>>>>>");
            }
        }).start();
        
    }
}

L'ajout d'un mot-clé synchronisé au niveau Java ajoutera un verrou invisible par défaut au niveau de la couche inférieure, Monitor 锁comme indiqué ci-dessous :

Insérer la description de l'image ici

Alors, Monitorquel est le rapport avec les objets et les threads Java ?

  1. Si un objet Java est verrouillé par un thread, le mot de verrouillage dans le champ de mot-clé de l'objet pointera vers l' Monitoradresse de départ.
  2. MonitorLe champ Propriétaire stockera l’ID de thread qui possède le verrou d’objet associé.

3. Processus d'optimisation des verrouillages

(1) Pas de verrouillage

Regardez le code suivant sans verrouillage, comme indiqué ci-dessous :

public class ObjectMarkWordDemo {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Object abc = new Object();
        System.out.println(ClassLayout.parseInstance(abc).toPrintable());
    }
}

S'il n'y a pas de verrou, le mot-clé normal d'un objet dans la mémoire Java est le suivant :

Insérer la description de l'image ici

Les informations imprimées via Java sont les suivantes :

Insérer la description de l'image ici

Notez qu'en regardant les résultats ci-dessus, la case bleue indique 001qu'à ce moment 无锁状态, 无锁状态时les 31 bits dans la case rouge hashCodeen représentent l'un, dont l'un consiste à ignorer le remplissage 0. Cependant, il a été constaté que le hashCode n'était pas affiché car cette opération 懒加载nécessite un ajustement de méthode pour déclencher le hashCode. Par exemple, le code suivant :

public class ObjectMarkWordDemo {
    
    
    public static void main(String[] args) {
    
    
		MyObject myObject = new MyObject();
        System.out.println("十进制表示: myObject.hashCode() = " + myObject.hashCode());
        System.out.println("二进制表示:"+Integer.toBinaryString(myObject.hashCode()));
        System.out.println("十六进制表示:"+Integer.toHexString(myObject.hashCode()));
        System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
    }
}

Le résultat est le suivant :

十进制表示: myObject.hashCode() = 1435804085
二进制表示:1010101100101001010000110110101
十六进制表示:5594a1b5

Insérer la description de l'image ici

Pour faciliter l'observation, imprimez chaque chiffre du codage hashCode. Commencez la copie de droite à gauche ( en commençant de droite à gauche, 8 octets sont copiés pour former une longue chaîne. Les 25 premiers bits appartiennent unusedau hashCode (cadre bleu), et le cadre rouge 3 Le bit indique le verrouillage- associé et 001indique 无锁le statut ). La première copie : 1010101 (le premier 0 est un complément, ne pas copier, c'est juste un des bits inutilisés dans les 25 premiers bits), la deuxième copie : 10010100, la troisième copie : 10100001, la quatrième copie : 10110101 Puis la chaîne est exactement la même que le système secondaire imprimé ci-dessus (1010101100101001010000110110101).Ces 31 bits sont le hashCode stocké.

Il ressort également de ce qui précède que l'état sans verrouillage à ce moment-là est 001représenté par .

(2) Verrouillage de biais

Lorsqu'un morceau de code synchronisé a été accédé plusieurs fois par le même thread, puisqu'il n'y a qu'un seul thread, le thread acquerra automatiquement le verrou lors des accès ultérieurs, comme le montre la figure suivante (encadré rouge) :

Insérer la description de l'image ici

Tant qu'un thread acquiert le verrou biaisé, le pointeur de thread actuel sera enregistré dans les 54 premiers bits de cet objet ( 无锁enregistrez le code hashCode) et la position du verrouillage biaisé sera définie sur 1.

Pourquoi avons-nous besoin de 2 bits pour représenter l’indicateur de verrouillage ?
Plus précisément, le verrou synchronisé est initialement dans un état sans verrou. Lorsque le premier thread est en compétition pour le verrou, il modifie l'indicateur de verrouillage dans l'en-tête de l'objet en un verrou biaisé, puis enregistre l'ID du thread dans l'en-tête de l'objet, indiquant que le fil a obtenu un blocage de biais. Lorsque le deuxième thread est en compétition pour le verrou, s'il constate que l'ID de thread enregistré dans l'en-tête de l'objet est cohérent avec l'ID de thread actuel, alors il peut obtenir le verrou. Sinon, le verrou de biais doit être révoqué et converti en un verrou léger. état de verrouillage. Lorsque plusieurs threads se disputent un verrou, ils entrent dans un état de verrouillage lourd. Par conséquent, afin de mettre en œuvre le processus de mise à niveau du verrou, Java ajoute deux bits à l'en-tête de l'objet pour représenter l'indicateur de verrouillage afin de réaliser le processus de conversion d'un état à l'autre, 无锁puis 偏向锁d' 轻量级锁un état à l'autre et enfin d'un 重量级锁état à l'autre.
Il y a quatre situations ici, donc 2 bits est utilisé pour représenter les quatre situations ci-dessus.

Si un thread exécute synchornizedle bloc de code, la JVM utilise l'opération CAS pour enregistrer l'ID du pointeur de thread dans le mot de marque et modifie le bit de verrouillage de biais pour indiquer que le thread actuel a obtenu le verrou. L'objet verrou devient 偏向锁(modifiez l'indicateur de verrouillage dans l'en-tête de l'objet via CAS).Après avoir exécuté le bloc de code synchronisé, le thread ne libérera pas activement le verrou de biais.

Le thread acquiert le verrou et peut exécuter le bloc de code synchronisé. Lorsque le thread 2 atteint le bloc de code synchronisé, il déterminera si le thread détenant le verrou à ce moment est lui-même. S'il s'agit de son propre ID de thread, cela signifie que le verrou de cet objet est toujours détenu et le bloc de code synchronisé peut continuent d'être exécutés. Étant donné que le verrouillage de biais n'a pas été activement libéré auparavant, il n'est pas nécessaire de le reverrouiller ici (il n'est pas nécessaire d'appeler à Mutexnouveau le verrouillage du système d'exploitation). S'il n'y a qu'un seul thread utilisant le verrou du début à la fin, il est évident que le verrou biaisé n'a ici presque aucune surcharge supplémentaire et les performances sont extrêmement élevées.

Vérifiez si le verrouillage de biais est activé

java -XX:+PrintFlagsInitial | grep BiasedLock

résultat de l'opération :

intx BiasedLockingBulkRebiasThreshold          = 20                                  {
    
    product}
intx BiasedLockingBulkRevokeThreshold          = 40                                  {
    
    product}
intx BiasedLockingDecayTime                    = 25000                               {
    
    product}
// 然后偏向锁开启之后默认会有4s钟的延迟,测试的时候需要注意,可以将这个值设置成0,方便查看效果
intx BiasedLockingStartupDelay                 = 4000                                {
    
    product}
bool TraceBiasedLocking                        = false                               {
    
    product}
// JVM 默认开启了偏向锁的设置
bool UseBiasedLocking                          = true                                {
    
    product}

Activez le paramètre de verrouillage de biais

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

En montrant les résultatsUseBiasedLocking = vraiOn peut savoir que la JVM active le verrouillage de biais par défaut, mais elle n'active pas le verrouillage de biais immédiatement lorsque le programme démarre, mais nécessite un délai de 4 secondes avant que le verrouillage de biais ne soit réellement activé.

Pourquoi y a-t-il un délai de 4 secondes pour ouvrir le verrou de polarisation ?
Étant donné que l'acquisition d'un verrou biaisé nécessite un certain temps, la JVM n'active pas le verrou biaisé immédiatement lors de la création de l'objet. Au lieu de cela, la JVM attend un certain temps (4 secondes par défaut) après la création de l'objet pour observer son utilisation. Si un seul thread accède à l'objet pendant cette période, la JVM définira l'indicateur de verrouillage de l'objet sur un verrou biaisé et enregistrera l'ID du thread dans l'en-tête de l'objet, indiquant que le thread a acquis le verrou de l'objet. Si plusieurs threads accèdent à l'objet pendant cette période, la JVM ne définira pas l'indicateur de verrouillage de l'objet sur un verrou biaisé, mais définira directement l'indicateur de verrouillage sur un verrou léger ou un verrou lourd. Utilisez les méthodes de verrouillage habituelles.

Cette stratégie consistant à attendre un certain temps avant d'activer les verrous biaisés vise à éviter de créer et de détruire fréquemment des objets sur une courte période de temps, ce qui rendrait la surcharge des verrous biaisés supérieure à la perte de performances du verrouillage.

L'effet de démonstration est que le délai de verrouillage de polarisation doit être réglé sur 0 s, comme le montre la figure :

Insérer la description de l'image ici
Les commandes dans la VM sont les suivantes :

-XX:BiasedLockingStartupDelay=0

Le code démo est le suivant :

public class ObjectMarkWordDemo {
    
    
    public static void main(String[] args) {
    
    

        Object abc = new Object();
        new Thread(() -> {
    
    
            synchronized (abc) {
    
    
                // 注意这里不要写任何代码操作
                System.out.println(ClassLayout.parseInstance(abc).toPrintable());
            }
        }).start();
    }
}

Remarque : n'écrivez pas d'autre code sur l'instruction de sortie ci-dessus

Le résultat est le suivant :

Insérer la description de l'image ici

Peut être modifié en disposition interne 偏向锁 101. Mais maintenant, seulement lorsqu'il n'y a pas de concurrence de verrouillage, si la concurrence est trouvée 锁撤销, elle procédera et libérera le verrou 轻量级锁.

(3) Annulation du verrouillage du biais

Insérer la description de l'image ici

Question : La révocation biaisée du verrou entraîne-t-elle une grave dégradation des performances ?

偏向锁撤销Il fait référence au processus de restauration de l'état de verrouillage de l'objet vers un état sans verrouillage en raison de la concurrence ou d'autres raisons dans l'état de verrouillage biaisé. L'annulation du verrouillage de biais fait référence à l'annulation du verrouillage de biais et au retour à l'état sans verrouillage.

Dans l'état de verrouillage biaisé, si d'autres threads tentent d'acquérir le verrou, le verrou biaisé doit d'abord être révoqué. Le processus de révocation du verrou biaisé nécessite de vérifier si le hashCode de l'objet a changé. Si le hashCode change, le verrou biaisé doit être révoqué. Sinon, le verrou peut être directement mis à niveau vers un verrou léger. Lors du processus d'annulation du verrouillage de biais, il est nécessaire de re-biaiser, d'effacer l'indicateur de verrouillage de biais, de définir l'ID du fil sur 0, etc.

Le processus de révocation biaisée des verrous est relativement gourmand en performances, il est donc nécessaire d'éviter autant que possible la révocation biaisée des verrous, en particulier dans les scénarios à forte concurrence.

Optimisation : dans le cas d'incitations compétitives, les verrous biaisés peuvent être désactivés et directement mis à niveau vers des verrous légers.

偏向锁撤销Le cas est le suivant :

public class ObjectMarkWordDemo {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    

        Object abc = new Object();

        synchronized (abc) {
    
    
            System.out.println("偏向锁:" + ClassLayout.parseInstance(abc).toPrintable());
        }
        System.out.println("偏向锁:" + ClassLayout.parseInstance(abc).toPrintable());
        new Thread(() -> {
    
    
            synchronized (abc) {
    
    	
            	System.out.println(">>>>>>发生竞争锁,触发偏向锁撤销...");
            }
        }).start();
        System.out.println("偏向锁撤销:" + ClassLayout.parseInstance(abc).toPrintable());
    }
}

Insérer la description de l'image ici

(4) Verrouiller l'enregistrement

Question : Qu'est-ce que Lock Record ?

Insérer la description de l'image ici

Le thread A créera un espace dans le cadre de pile pendant l'exécution, appelé Lock Recordenregistrement, pour stocker les enregistrements de verrouillage. Lorsque la machine virtuelle détecte que cet objet est dans un état sans verrou, elle créera cet espace sur le frame de pile de ce thread pour stocker les Mark Wordinformations relatives au verrou.

Lock RecordLes données à l'intérieur sont toutes copiées Mark Wordà l'intérieur, car les informations relatives au verrouillage s'y Mark Wordtrouvent. Parallèlement, le nom officiel de ce processus de copie est : Displaced Mark Word. Enfin, grâce à l'opération CAS spin, le pointeur de ce cadre de pile est écrit dans le mot de marque.Si l'écriture réussit, cela signifie que le thread A a réussi à acquérir le verrou. Si l'écriture échoue, cela signifie que le verrou est occupé par d'autres threads.

(5) Serrure légère

轻量级锁交替执行同步Pour améliorer l'efficacité lorsque les threads sont proches du code.

CASL'objectif principal est de réduire la consommation de performances causée par les verrous lourds utilisant des mutex du système d'exploitation sans concurrence multithread. Pour le dire franchement, tournez d'abord puis bloquez. Synchronisation de mise à niveau, lorsque la fonction de verrouillage de biais est désactivée ou que plusieurs threads sont en compétition pour le verrouillage de biais, il sera mis à niveau vers un verrouillage léger.

Si le thread A a déjà obtenu le verrou, alors le thread B vient arracher le verrou de l'objet. Puisque le verrou de l'objet a été obtenu par le thread A, le verrou est actuellement un verrou de biais ; et le thread B est en compétition pour trouver le verrou. thread dans Makr Word. Si l'ID n'est pas le sien, le thread B entrera dans l'opération de spin CAS dans l'espoir d'obtenir le verrou. À l’heure actuelle, il existe deux situations dans le fonctionnement du thread B :

① Si le verrou est acquis avec succès, remplacez directement l'ID du thread dans Mark Word par le propre ID du thread B, puis déplacez-le vers le thread B. Le verrou reste dans l'état de verrouillage biaisé, le thread A se termine et le thread B prend le relais.

Insérer la description de l'image ici

② Si l'acquisition du verrou échoue, le verrou biaisé est mis à niveau vers un verrou léger. À ce moment, le verrou léger est détenu par le thread qui détenait à l'origine le verrou biaisé et continue d'exécuter son bloc de code de synchronisation, tandis que le thread concurrent B le fera. entrez spin Attendez ce verrou léger.

Insérer la description de l'image ici

Le verrou léger tourne trop de fois, ce qui entraîne un gaspillage de ressources CPU. Avant JDK6, la rotation par défaut était 10 fois ou le nombre de threads de rotation dépassait la moitié du nombre de cœurs de processeur et la rotation était immédiatement abandonnée et mise à niveau vers 重量级锁 10.

Modifier la commande du nombre de tours :

-XX:PreBlockSpin=10

轻量级锁Le cas est le suivant (le délai de verrouillage du biais peut être restauré) :

-XX:BiasedLockingStartupDelay=4000
package com.xxl.job.admin.mytest;


import org.openjdk.jol.info.ClassLayout;

import java.util.concurrent.TimeUnit;

public class ObjectMarkWordDemo {
    
    

    public void test() {
    
    
        Object obj = new Object();
        synchronized (obj) {
    
    
            System.out.println("111");
        }
        synchronized (obj) {
    
    
            System.out.println("111");
        }
        synchronized (obj) {
    
    
            System.out.println("111");
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        // 打印 JVM 相关的信息
        // System.out.println(VM.current().details());
        // 打印每个对象是否为 8 的整数倍大小
        // System.out.println(VM.current().objectAlignment());
        MyObject myObject = new MyObject();
        System.out.println(Integer.toHexString(myObject.hashCode()));
        new Thread(()->{
    
    
            // 在 myObject 对象头上进行加锁(默认直接干到轻量级锁,这里我非要把他干到偏向锁状态)
            // 默认是开启偏向锁的,所以这里我们只需要把开启偏向锁的延迟时间修改成 0 方便看效果 -XX:+BiasedLockingStartupDelay=0
            synchronized (myObject) {
    
    
                // 给这个线程加锁,并且还设置了偏向线程 ID
                System.out.println(ClassLayout.parseInstance(myObject).toPrintable());
            }
        }).start();

        TimeUnit.MICROSECONDS.sleep(500);

        // 锁被释放了,所以这里打印的肯定是无锁状态 001
        System.out.println(ClassLayout.parseInstance(myObject).toPrintable());

    }
}
class MyObject {
    
    

}

résultat de l'opération :

76fb509a
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.xxl.job.admin.mytest.MyObject object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           e8 29 c3 0a (11101000 00101001 11000011 00001010) (180562408)
      4     4        (object header)                           03 00 00 00 (00000011 00000000 00000000 00000000) (3)
      8     4        (object header)                           44 c1 00 f8 (01000100 11000001 00000000 11111000) (-134168252)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.xxl.job.admin.mytest.MyObject object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           e8 29 c3 0a (11101000 00101001 11000011 00001010) (180562408)
      4     4        (object header)                           03 00 00 00 (00000011 00000000 00000000 00000000) (3)
      8     4        (object header)                           44 c1 00 f8 (01000100 11000001 00000000 11111000) (-134168252)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

(6) Serrure lourde

Lorsque les verrous légers continuent de tourner, vous devez vous demander si l’utilisation de verrous lourds peut améliorer les performances.

public class ObjectMarkWordDemo {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    

        Object abc = new Object();
        for (int i = 0; i < 2; i++) {
    
    
            Thread thread = new Thread(() -> {
    
    
                synchronized (abc) {
    
    
                    System.out.println("重量级锁:" + ClassLayout.parseInstance(abc).toPrintable());
                }
            });
            thread.join();
            thread.start();
        }
    }
}

Insérer la description de l'image ici

(7) Dégrossissage du verrou

Similaire à l'exemple suivant :

public class ObjectMarkWordDemo {
    
    
    Object abc = new Object();

    public  void show() {
    
    
        synchronized (abc) {
    
    
            // 复杂操作
        }
        synchronized (abc) {
    
    
            // 复杂操作
        }
        synchronized (abc) {
    
    
            // 复杂操作
        }
    }
}

Le même verrou est toujours utilisé et le temps d'exécution avant et après est très court.La JVM optimisera et fusionnera directement ces verrous en un seul grand verrou, qui peut être appelé pour assurer les performances du programme 锁膨胀.

(8) Élimination du verrouillage

Similaire à l'exemple suivant :

public class ObjectMarkWordDemo {
    
    

    public void show() {
    
    
        Object abc = new Object();
        synchronized (abc) {
    
    
            // 复杂操作
        }
    }
 }

La méthode show() se verrouillera à chaque fois, mais ce verrou n'a aucune signification, donc la couche inférieure de la JVM l'optimisera pour améliorer les performances du programme.

3. Commandes couramment utilisées

Définir la taille du tas JVM

-Xms10m -Xmx10m

Rechercher quelles commandes sont exécutées au démarrage de la JVM

java -XX:+PrintCommandLineFlags -version

Désactiver la configuration de compression des pointeurs de classe dans les en-têtes d'objet

-XX:-UseCompressedClassPointers

Vérifiez si le verrouillage de biais est activé

java -XX:+PrintFlagsInitial | grep BiasedLock

résultat de l'opération :

intx BiasedLockingBulkRebiasThreshold          = 20                                  {
    
    product}
intx BiasedLockingBulkRevokeThreshold          = 40                                  {
    
    product}
intx BiasedLockingDecayTime                    = 25000                               {
    
    product}
// 然后偏向锁开启之后默认会有4s钟的延迟,测试的时候需要注意,可以将这个值设置成0,方便查看效果
intx BiasedLockingStartupDelay                 = 4000                                {
    
    product}
bool TraceBiasedLocking                        = false                               {
    
    product}
// JVM 默认开启了偏向锁的设置
bool UseBiasedLocking                          = true                                {
    
    product}

Activer le paramètre de verrouillage de biais

-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

Acho que você gosta

Origin blog.csdn.net/qq_35971258/article/details/129286257
Recomendado
Clasificación