Maison  >  Article  >  Java  >  Comprendre les optimisations de verrouillage JVM

Comprendre les optimisations de verrouillage JVM

DDD
DDDoriginal
2024-09-23 16:16:39362parcourir

Understanding JVM Lock Optimizations

여러 동시 작업을 수행할 수 있는 강력하고 확장 가능한 애플리케이션을 개발하려면 동시성이 매우 중요합니다. 그러나 이를 위해서는 동기화 측면에서 대가를 지불해야 합니다. 잠금 획득 및 해제에 수반되는 오버헤드로 인해 성능 비용이 발생합니다. 이러한 성능 비용을 줄이기 위해 편향된 잠금, 잠금 제거, 잠금 거칠기화, 경량 및 중량 잠금 개념 등 여러 가지 최적화 기능이 JVM에 통합되었습니다.

이 기사에서는 이러한 최적화에 대해 더 자세히 살펴보고 멀티 스레드 Java 애플리케이션에서 동기화를 향상시키는 방법을 살펴봅니다.

Java 잠금 기본 사항

Java에서는 블록이나 메소드의 동기화를 통해 한 번에 하나의 스레드만 코드의 중요한 섹션을 실행할 수 있습니다. 이는 다중 스레드 환경 내에서 리소스 공유를 고려할 때 특히 중요합니다. Java는 내장 잠금을 사용하여 이를 구현합니다. 때로는 동기화된 블록을 사용하여 스레드에 대한 액세스를 관리하는 데 도움이 되는 개체 또는 클래스와 연결된 모니터라고도 합니다.

스레드 안전성을 위해서는 동기화가 필수이지만 경합이 낮거나 전혀 없을 경우 비용이 상당히 많이 들 수 있습니다. JVM 최적화가 프레임에 들어가는 곳입니다. 따라서 잠금 비용이 절감되고 전반적인 성능이 향상됩니다.

1. 편향된 잠금

편향 잠금이란 무엇인가요?

편향된 잠금은 잠금 획득 오버헤드를 줄이는 것을 목표로 하는 최적화입니다. 이는 단일 스레드에 의해 지배되거나 단일 스레드에 의해 가장 많이 액세스되는 잠금 획득 비용을 줄이기 위해 최적화되었습니다. 이러한 프로그램은 종종 다른 스레드와의 경합 없이 동일한 스레드에 의해 잠금을 획득하고 해제합니다. JVM은 이 패턴을 인식하고 해당 특정 스레드에 대한 잠금을 바이어스할 수 있습니다. 다음 잠금 획득은 거의 무료입니다.

편향 잠금은 어떻게 작동하나요?

편향 잠금이 활성화되면 스레드가 처음으로 잠금을 획득할 때 잠금이 해당 스레드 쪽으로 편향되게 됩니다. 스레드의 ID는 잠금 개체의 헤더에 기록되며 해당 스레드에 의한 후속 잠금 획득에는 동기화가 전혀 포함되지 않습니다. 잠금이 현재 스레드 쪽으로 편향되어 있는지 확인하기만 하면 됩니다. 이는 매우 빠르고 비차단 작업입니다. .

다른 스레드가 잠금을 획득하려고 하면 바이어스가 취소되고 JVM은 바이어스되지 않은 표준 잠금 메커니즘으로 돌아갑니다. 이 단계에서는 이제 표준 잠금이 되며 두 번째 스레드는 표준 잠금 프로세스를 통해 이를 획득해야 합니다.
편향 잠금의 이점

성능: 바이어스 잠금에서 동일한 스레드 획득은 거의 무료 잠금 획득입니다.

따라서 다른 스레드가 잠금 획득에 참여할 기회가 없으므로 경합 처리가 필요하지 않습니다.

낮은 오버헤드: 경합이 발생하는 경우를 제외하고는 잠금 상태를 변경하거나 동기화 관련 메타데이터를 수정할 필요가 없습니다. 
 

편향 잠금은 언제 사용되나요?

편향된 잠금은 단일 스레드 애플리케이션이나 다중 스레딩에서 잠금 경합이 낮은 애플리케이션과 같이 주로 동일한 스레드에서 잠금에 액세스하는 애플리케이션에 유용합니다. 대부분의 JVM에서는 기본적으로 활성화되어 있습니다.

편향 잠금 비활성화 방법

편향 잠금은 기본적으로 활성화되어 있지만 아래와 같이 JVM 플래그를 사용하여 비활성화할 수도 있습니다.

-XX:-BiasedLocking 사용

2. 잠금 제거

잠금 제거란 무엇인가요?

잠금 제거는 JVM이 일부 불필요한 동기화(잠금)를 완전히 제거하는 매우 강력한 최적화입니다. JIT 컴파일 중에 코드를 검사하여 동기화가 필요하지 않음을 확인합니다. 이는 일반적으로 하나의 스레드에서만 잠금에 액세스했거나 JVM을 사용하여 동기화하는 개체가 다른 스레드 내에서 동일한 개체를 공유하지 않는 경우에 발생합니다. JVM이 더 이상 필요하지 않다고 판단하면 잠금이 제거됩니다.

잠금 제거는 어떻게 작동하나요?

JIT 컴파일의 이스케이프 분석 단계에서 JVM은 객체가 단일 스레드에 국한되어 있는지 아니면 로컬 컨텍스트에서만 사용되는지 확인합니다. 개체가 자신을 생성한 스레드의 범위를 벗어나지 않기 때문에 해당 개체에 대한 동기화를 제거할 수 있다면 그렇게 됩니다.

Par exemple, si un objet est créé et utilisé entièrement dans une méthode (et non partagé entre les threads), la JVM se rend compte qu'aucun autre thread ne peut accéder à l'objet, et donc que toute synchronisation est redondante. Dans un tel cas, le compilateur JIT élimine simplement complètement le verrou.

Zéro surcharge de verrouillage : L'élimination des synchronisations inutiles empêchera également la JVM de payer le coût d'acquisition et de libération des verrous en premier lieu.

Débit plus élevé : La synchronisation morte peut parfois conduire à un débit plus élevé de l'application, surtout si le code contient de nombreux blocs synchronisés.

Jetez un œil à ce morceau de code :

public void someMethod() {
    StringBuilder sb = new StringBuilder();
    synchronized (sb) {
        sb.append("Hello");
        sb.append("World");
    }
}

Dans ce cas, la synchronisation sur sb n'est pas nécessaire puisque le StringBuilder est utilisé uniquement dans le someMethod et n'est pas partagé entre d'autres threads. En regardant cela, la JVM peut effectuer une analyse d'échappement pour supprimer le verrou.

3. Grossissement du verrouillage

Qu'est-ce que le verrouillage grossissant ?

Le grossissement du verrouillage est une optimisation dans laquelle la JVM étend la portée d'un verrou pour couvrir plus de morceaux de code au lieu d'acquérir et de libérer continuellement le verrou dans des boucles ou de petites sections de code.

Travail de grossissement des verrous

Si la JVM constate qu'une boucle serrée ou plusieurs blocs de code adjacents acquièrent et libèrent un verrou trop fréquemment, elle peut rendre le verrou plus grossier en le plaçant en dehors de la boucle ou sur plusieurs blocs de code. Cela rend l'acquisition et la libération répétées du sans verrou coûteuses et permet à un thread de maintenir le verrou pendant plus d'itérations.

Exemple de code : grossissement du verrouillage

Considérez cet extrait de code :

for (int i = 0; i < 1000; i++) {
    synchronized (lock) {
        // Do something
    }
}

Le grossissement du verrou pousse l'acquisition du verrou en dehors de la boucle, de sorte que le thread n'acquiert le verrou qu'une seule fois :

synchronized (lock) {
  for (int i = 0; i < 1000; i++) {
    // Do something
  }
}

La JVM peut améliorer considérablement les performances en évitant davantage d'acquisitions et de libérations du verrou.

Avantages du grossissement des verrous

Moins de liberté de surcharge de verrouillage : Le grossissement évite les acquisitions et les libérations de verrous, en particulier dans le code de point chaud, comme les boucles qui ont été itérées des milliers de fois.

Performances améliorées :
Le verrouillage pendant une période plus longue améliore les performances par rapport au scénario dans lequel, sans verrouillage, un tel verrou serait acquis et libéré plusieurs fois.

4. Serrures légères et lourdes

La JVM utilise deux techniques de verrouillage différentes en fonction du degré de conflit entre les threads. Ces techniques incluent les serrures légères et les serrures lourdes.

Verrouillage léger

Le verrouillage léger a lieu en l'absence de verrou de contention, ce qui signifie qu'un seul thread tente d'acquérir ce verrou. Dans de tels scénarios, la JVM optimise l'acquisition à l'aide d'une opération CAS lors de la tentative d'acquisition du verrou, ce qui peut se produire sans synchronisation lourde.

Verrouillage lourd

Dans le cas où plusieurs threads souhaitent obtenir le même verrou ; c'est-à-dire qu'il y a conflit, la JVM élève cela au verrouillage lourd. Cela impliquerait de bloquer les threads au niveau du système d'exploitation et de les gérer à l'aide de primitives de synchronisation au niveau du système d'exploitation. Les verrous lourds sont plus lents car ils nécessitent en fait que le système d'exploitation effectue un changement de contexte, ainsi que pour gérer les threads.

Escalade de verrouillage

Si un conflit survient au niveau d'un verrou léger, la JVM peut le transformer en un verrou lourd. L'escalade signifie ici passer du verrouillage rapide au niveau de l'utilisateur à un verrouillage plus coûteux au niveau du système d'exploitation qui inclut le blocage des threads.

Avantages des serrures légères

Acquisition rapide d'un verrou : Lorsqu'il n'y a pas de conflit, les verrous légers sont beaucoup plus rapides que les verrous lourds car ils évitent la synchronisation au niveau du système d'exploitation.

Blocage réduit : Sans conflits, les threads ne bloquent pas et augmentent linéairement avec une latence plus faible.

Inconvénients des serrures lourdes

Surcharge de performances : les verrous lourds entraînent le coût du blocage des threads, du changement de contexte et du réveil des threads avec une dégradation des performances dans des régimes de contention très élevés.

Toutes ces optimisations aident la JVM à améliorer les performances dans les applications multithread, de sorte que les développeurs peuvent désormais écrire du code simultané et sécurisé sans trop sacrifier la surcharge de synchronisation. Comprendre ces optimisations peut aider les développeurs à concevoir des systèmes plus efficaces, en particulier dans les cas où le verrouillage entraîne une pénalité de haute performance.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn