JMM이라고하는 Java 메모리 모델은 메모리 가시성을위한 관련없는 특정 플랫폼에 대한 일련의 Java 가상 머신 플랫폼에 대한 통합 보증이며 개발자가 제공하는 다중 스레드 환경에서 재정렬 할 수 있는지 여부입니다. (용어 및 Java 런타임의 메모리 분포 측면에서 모호 할 수 있으며, 이는 힙, 메소드 영역, 스레드 스택 등과 같은 메모리 영역을 나타냅니다).
동시 프로그래밍 스타일이 많이 있습니다. CSP (Communication Sequential Process), 액터 및 기타 모델 외에도 가장 친숙한 것은 스레드와 잠금을 기반으로 한 공유 메모리 모델이어야합니다. 다중 스레드 프로그래밍에서는 다음에주의를 기울여야합니다.
・ 원자력 om 가시성 ・ 리더
원자력은 다른 스레드가 중간 상태를 볼 수 있는지 또는 스레드가 복합 작업을 수행 할 때 방해하는지 여부를 포함합니다. 일반적으로 I ++의 문제입니다. 두 개의 스레드는 공유 힙 메모리에서 동시에 ++ 작업을 수행합니다. JVM, 런타임 및 CPU에서 ++ 작업을 구현하는 것은 복합 작업 일 수 있습니다. 예를 들어, JVM 명령의 관점에서, 힙 메모리에서 피연산자 스택까지의 I의 값을 읽고, 하나를 추가하고, 힙 메모리 i에 다시 기록하는 것입니다. i. 이러한 작업 중에 올바른 동기화가 없으면 다른 스레드도 동시에 실행할 수있어 데이터 손실 및 기타 문제로 이어질 수 있습니다. 경쟁 조건이라고도하는 일반적인 원자력 문제는 read-modify-write와 같은 가능한 실패 결과에 따라 판단됩니다. 가시성 및 재정렬 문제는 모두 시스템 최적화에서 비롯됩니다.
CPU의 실행 속도와 메모리의 액세스 속도가 시간 위치 및 공간 위치와 같은 현지화 원리를 기반으로 성능을 최적화하기 위해 심각한 일치하지 않기 때문에 CPU는 메모리간에 멀티 레이어 캐시를 추가했습니다. 데이터를 가져와야 할 때 CPU는 먼저 캐시로 이동하여 해당 캐시가 존재하는지 확인합니다. 존재하면 직접 반환됩니다. 존재하지 않으면 메모리로 가져와 캐시에 저장됩니다. 이제 다중 코어 프로세서가 표준이 될수록 각 프로세서에는 자체 캐시가있어 캐시 일관성 문제가 포함됩니다. CPU는 다양한 강점과 약점의 일관성 모델을 가지고 있습니다. 가장 강력한 일관성은 가장 높은 보안이며 순차적 사고 모드를 준수합니다. 그러나 성능 측면에서 다른 CPU 간의 조정 된 통신이 필요하기 때문에 많은 오버 헤드가있을 것입니다.
일반적인 CPU 캐시 구조 다이어그램은 다음과 같습니다
CPU의 명령주기는 일반적으로 데이터를 읽기위한 지침, 지침 실행 및 레지스터 또는 메모리에 데이터를 작성하는 지시 사항을 사용합니다. 직렬의 지침을 실행할 때 읽기 및 저장된 데이터는 오랜 시간이 걸리므로 CPU는 일반적으로 공장 파이프 라인과 마찬가지로 전체 처리량을 개선하기 위해 여러 명령을 동시에 실행하기 위해 명령어 파이프 라인을 사용합니다.
데이터를 읽고 메모리에 데이터를 작성하는 속도는 지침을 실행하는 것과 같은 순서가 아니므로 CPU는 레지스터와 캐시를 캐시 및 버퍼로 사용합니다. 메모리에서 데이터를 읽을 때 캐시 라인을 읽습니다 (디스크 읽기와 유사한 블록과 비슷합니다). 기존 데이터가 캐시에없고 명령주기의 다음 단계를 계속 실행할 때 데이터를 뒤로 둔 모듈은 스토리지 요청을 스토어 버퍼에 넣습니다. 캐시에 존재하면 캐시가 업데이트되고 캐시의 데이터가 특정 정책에 따라 메모리로 플러시됩니다.
공개 클래스 메모리 모델 {private int count; 개인 부울 정지; public void initCountAndStop () {count = 1; 정지 = 거짓; } public void doloop () {while (! stop) {count ++; }} public void printresult () {system.out.println (count); System.out.println (정지); }}위의 코드를 실행할 때는 Count = 1이 stop = false 전에 실행될 것이라고 생각할 수 있습니다. 이것은 위의 CPU 실행 다이어그램에 표시된 이상적인 상태에서 정확하지만 레지스터 및 캐시 버퍼링을 고려할 때는 올바르지 않습니다. 예를 들어, 정지 자체가 캐시에 있지만 카운트가 없으면 정지가 업데이트 될 수 있고 카운트 쓰기 버퍼가 메모리로 새로 고침됩니다.
또한 CPU 및 컴파일러 (일반적으로 JIT 참조 JIT 참조)는 명령 실행 순서를 수정할 수 있습니다. 예를 들어, 위의 코드에서 count = 1 및 spop = false에는 종속성이 없으므로 CPU와 컴파일러는이 두 가지의 순서를 수정할 수 있습니다. 단일 스레드 프로그램의 관점에서 결과는 동일합니다. 이것은 또한 CPU와 컴파일러가 보장 해야하는 AS-IF- 서리얼입니다 (실행 순서가 수정 된 방법에 관계없이 단일 스레드의 실행 결과는 변경되지 않았습니다). 대부분의 프로그램 실행은 단일 스레드이기 때문에 이러한 최적화는 허용되며 성능 향상을 제공합니다. 그러나 멀티 스레딩의 경우 필요한 동기화 작업없이 예기치 않은 결과가 발생할 수 있습니다. 예를 들어, 스레드 t1이 initCountAndStop 메소드를 실행 한 후 스레드 T2는 0, false, 1, false 또는 0, true 일 수있는 printresult를 실행합니다. Thread T1이 Doloop ()를 먼저 실행하고 스레드 T2가 INITCOUNTANDSTOP을 1 초간 실행하면 T1이 루프에서 벗어나거나 컴파일러 최적화로 인해 정지 수정을 볼 수 없을 수도 있습니다.
위의 다중 스레딩 상황에서 다양한 문제로 인해 멀티 스레딩의 프로그램 시퀀스는 더 이상 실행 순서가 아니며 기본 메커니즘을 초래합니다. 프로그래밍 언어는 개발자에게 보증을 제공해야합니다. 간단히 말해서,이 보증은 스레드의 수정이 다른 스레드에 표시 될 때입니다. 따라서 Java Language는 JavamemoryModel, 즉 Java 메모리 모델을 제안하며,이 모델의 규칙에 따라 구현이 필요합니다. Java는 휘발성, 동기화 및 최종과 같은 메커니즘을 제공하여 개발자가 모든 프로세서 플랫폼에서 다중 스레드 프로그램의 정확성을 보장 할 수 있도록 도와줍니다.
JDK1.5 이전에 Java의 메모리 모델에는 심각한 문제가있었습니다. 예를 들어, 이전 메모리 모델에서 스레드는 생성자가 완료된 후 최종 필드의 기본값을 볼 수 있으며, 휘발성 필드의 쓰기는 비 휘발성 필드의 읽기 및 쓰기로 재정렬 될 수 있습니다.
따라서 JDK1.5에서는 이전 문제를 해결하기 위해 JSR133을 통해 새로운 메모리 모델이 제안되었습니다.
재주문 규칙
휘발성 및 모니터 잠금
| 재주문 할 수 있습니까? | 두 번째 작업 | 두 번째 작업 | 두 번째 작업 |
|---|---|---|---|
| 첫 번째 작업 | 정상적인 읽기/일반 글쓰기 | 휘발성 읽기/모니터 입력 | 휘발성 쓰기/모니터 종료 |
| 정상적인 읽기/일반 글쓰기 | 아니요 | ||
| Voaltile 읽기/모니터 입력 | 아니요 | 아니요 | 아니요 |
| 휘발성 쓰기/모니터 종료 | 아니요 | 아니요 |
정상적인 읽기는 getfield, getstatic 및 volatile 어레이의 배열을 말하며, 정상적인 읽기는 Putfield, 퍼팅 스테이션 및 비 휘발성 어레이의 배열을 나타냅니다.
휘발성 필드의 읽기 및 쓰기는 각각 Getfield, Getstatic, Putfield, Putstatic입니다.
MonitorEnter는 동기화 블록 또는 동기화 메소드를 입력하는 것이 좋습니다. Monitorexist는 동기화 블록 또는 동기화 메소드를 종료하는 것을 나타냅니다.
위의 표에서 No는 재정렬을 허용하지 않는 두 개의 작업을 나타냅니다. 예를 들어 (정상 쓰기, 휘발성 쓰기)는 비 휘발성 필드의 재정렬 및 후속 휘발성 필드의 글의 재주문을 나타냅니다. 아니오가 없을 때 재주문이 허용되지만 JVM은 최소 보안을 보장해야한다는 것을 의미합니다. 읽기 값은 기본값이거나 다른 스레드가 작성한 것입니다 (64 비트 더블 및 긴 읽기 및 쓰기 작업은 특별한 경우입니다. 휘발성 수정이 없을 때 읽기 및 쓰기가 원자가되어 두 개의 별도의 작업으로 분할 될 수 있습니다).
최종 필드
최종 필드에 대한 두 가지 추가 특별 규칙이 있습니다.
최종 필드 (생성자)의 쓰기 또는 최종 필드 객체 자체의 기준 쓰기는 최종 필드 (생성자 외부)를 고정하는 객체의 후속 쓰기로 재정렬 할 수 없습니다. 예를 들어, 다음 진술을 재정렬 할 수 없습니다
x.finalfield = V; ...; SharedRef = x;
최종 필드의 첫 번째 하중은 최종 필드를 고정하는 물체의 쓰기로 재정렬 할 수 없습니다. 예를 들어, 다음 명령문은 재정렬을 허용하지 않습니다.
x = sharedref; ...; i = X.Finalfield
메모리 장벽
프로세서는 모두 특정 메모리 장벽 또는 울타리를 지원하여 다른 프로세서 간의 재주문 및 데이터를 제어합니다. 예를 들어, CPU가 데이터를 다시 작성하면 스토어 요청을 쓰기 버퍼에 넣고 메모리로 플러시를 기다립니다. 이 저장소 요청은 데이터의 가시성을 보장하기 위해 장벽을 삽입하여 다른 요청과 관련이없는 것을 방지 할 수 있습니다. 생명 예제를 사용하여 장벽을 비교할 수 있습니다. 예를 들어, 지하철에서 경사 엘리베이터를 타면 모든 사람이 엘리베이터로 순서대로 들어가지만 일부 사람들은 왼쪽에서 돌아 다니면 엘리베이터를 떠날 때의 순서가 다릅니다. 사람이 큰 수하물을 차단 한 (장벽)를 가지고 다니면 뒤에있는 사람들은 돌아 다닐 수 없습니다 :). 또한, 여기서 장벽과 GC에 사용 된 쓰기 장벽은 다른 개념입니다.
메모리 장벽의 분류
거의 모든 프로세서는 일반적으로 울타리 (울타리, 울타리)라고 불리는 특정 거친 곡물의 장벽 지침을 지원하며, 이는 울타리 후에 부하 및 저장으로 울타리 전에 시작된 하중 및 저장 지침을 보장 할 수 있습니다. 일반적으로, 그것은 그들의 목적에 따라 다음 4 가지 유형의 장벽으로 나뉩니다.
로드로드 장벽
로드 1; 로드; 로드 2;
Load1 데이터가로드 전과로드 후로드되는지 확인하십시오.
Storestore 장벽
상점 1; Storestore; Store2
Store1의 데이터가 Store2 이전 및 후 다른 프로세서에 표시되어 있는지 확인하십시오.
로드 스토어 장벽
로드 1; 로드 스토어; Store2
Load1의 데이터가 Store2 전과 데이터 플러시 후로드되는지 확인하십시오.
Storeload 장벽
상점 1; Storeload; 로드 2
로드 2에 데이터를로드하고로드 후 데이터를로드하기 전에 Store1의 데이터가 다른 프로세서 (예 : 메모리에 대한 플러싱) 앞에서 볼 수 있는지 확인하십시오. Storeload Barrier는 최근 다른 프로세서가 작성한 데이터보다는 이전 데이터를 읽는 것을 방지합니다.
현대의 거의 모든 멀티 프로세서에는 Storeload가 필요합니다. Storeload의 오버 헤드는 일반적으로 가장 크고 Storeload는 세 가지 장벽의 영향을 미치므로 Storeload는 일반 (그러나 더 높은 오버 헤드) 장벽으로 사용할 수 있습니다.
따라서 위의 메모리 배리어를 사용하여 위 표의 재주문 규칙을 구현할 수 있습니다.
| 장벽이 필요합니다 | 두 번째 작업 | 두 번째 작업 | 두 번째 작업 | 두 번째 작업 |
|---|---|---|---|---|
| 첫 번째 작업 | 정상적인 독서 | 정상적인 글쓰기 | 휘발성 읽기/모니터 입력 | 휘발성 쓰기/모니터 종료 |
| 정상적인 독서 | 로드 스토어 | |||
| 정상적인 독서 | Storestore | |||
| Voaltile 읽기/모니터 입력 | 로드 | 로드 스토어 | 로드 | 로드 스토어 |
| 휘발성 쓰기/모니터 종료 | Storeload | Storestore |
최종 필드의 규칙을 지원하기 위해서는 Final에 최종 쓰기에 장벽을 추가해야합니다.
x.finalfield = V; Storestore; SharedRef = x;
메모리 장벽을 삽입하십시오
위 규칙에 따라 휘발성 필드 및 동기화 된 키워드의 처리에 장벽을 추가하여 메모리 모델의 규칙을 충족 할 수 있습니다.
모든 최종 필드가 작성된 후 휘발성 상점 장벽 전에 Storestore를 삽입하지만 생성자가 반환되기 전에 Storestore를 삽입하십시오.
휘발성 저장소 후에 Storeload 장벽을 삽입하십시오. 휘발성 하중 후로드 및로드 스토어 장벽을 삽입하십시오.
모니터 입력 및 휘발성로드 규칙은 일관성이 있으며 모니터 종료 및 휘발성 저장 규칙은 일관됩니다.
앞서 일어나십시오
위에서 언급 한 다양한 메모리 장벽은 여전히 개발자에게 비교적 복잡하므로 JMM은 일련의 부분 순서 관계 규칙을 사용할 수 있습니다. 작동 B를 실행하는 스레드가 작동 A의 결과를 보도록하기 위해 (A와 B가 동일한 스레드에서 실행되는지 여부에 관계없이) A와 B 사이의 관계가 충족되기 전에 발생하기 전에 JVM이 임의로 재정렬 할 수 있습니다.
규칙 목록 이전에 발생합니다
규칙이 포함되기 전에 Happend
프로그램 시퀀스 규칙 : 프로그램의 작업 A가 작동 B 전인 경우 동일한 스레드의 작동 A는 작동하기 전에 모니터 잠금 규칙을 수행합니다.
휘발성 변수 규칙 : 휘발성 변수의 쓰기 작업은 변수의 읽기 작업 전에 스레드 스타트 업 규칙을 실행해야합니다. 스레드에서의 호출은 스레드에서 스레드 종료 규칙을 실행해야합니다. 스레드의 모든 작업이 스레드에서 작업을 수행하기 전에 스레드의 모든 스레드가 종료되기 전에 다른 스레드 호출이 방해하기 전에 스레드가 중단 될 때, 작동을 방해 해야하는 경우, 작동을 방해해야합니다. 작업 B가 작동 C 전에 실행되면 작동 A가 작동하기 전에 실행됩니다.
디스플레이 잠금은 모니터 잠금과 동일한 메모리 의미를 갖고 원자 변수는 휘발성과 동일한 메모리 의미를 갖습니다. 잠금의 획득 및 릴리스, 휘발성 변수의 읽기 및 쓰기 작업은 전체 주문 관계를 충족하므로, 휘발성의 쓰기는 후속 휘발성 읽기 전에 수행 될 수 있습니다.
위에서 언급 한 일은 여러 규칙을 사용하여 결합 할 수 있습니다.
예를 들어, 스레드 A가 모니터 잠금에 들어가면 모니터 잠금을 출시하기 전의 작업은 프로그램 시퀀스 규칙을 기반으로하며, 모니터 릴리스 작업이 발생하여 후속 스레드 B에서 동일한 모니터 잠금 및 스레드 B에서의 작동 작업을 얻는 데 사용됩니다.
요약
위의 내용은이 기사에서 Java Memory Model JMM에 대한 자세한 설명입니다. 모든 사람에게 도움이되기를 바랍니다. 단점이 있으면 메시지를 남겨 두십시오. 이 사이트를 지원해 주신 친구들에게 감사드립니다!