C 프로그램 코드에서는 운영 체제가 제공하는 MUTEX 잠금 장치를 사용하여 동기 블록 및 스레드 차단 및 웨이크 업 작업에 대한 MUTEX 액세스를 달성 할 수 있습니다. 그러나 Java에서는 Lockapi를 제공하는 것 외에도 Synchronized Keywords도 구문 수준에서 제공되어 MUTEX 동기화 프리미티브를 구현합니다. 그렇다면 JVM에서 동기화 된 키를 어떻게 구현합니까?
1. 동기화 된 바이트 코드 표현 :
Java 언어에는 두 개의 내장 동기화 구문이 있습니다. 1. 동기화 된 문; 2. 동기화 된 방법. Java 소스 코드가 Javac에 의해 Bytecode로 컴파일 될 때 동기화 된 진술의 경우, Moniterenter 및 MonitoreXit Bytecode 지침은 각각 동기화 블록의 입력 및 종료 위치에 삽입됩니다. 동기화 된 메소드는 일반 메소드 호출로 변환되고 다음과 같은 반환 지침 : InvokeVirtual 및 Areturn 지침. 동기화 된 수정 된 메소드를 구현하기위한 VM 바이트 코드 레벨에는 특별 명령이 없습니다. 대신, 메소드 access_flags 메소드의 동기화 된 플래그 위치 1은 클래스 파일의 메소드 테이블에 배치되며, 메소드가 동기화 된 메소드이고 메소드를 호출하는 객체 또는 메소드를 잠금 오브젝트로 표시하는 클래스를 사용한다는 것을 나타냅니다.
2. JVM의 잠금 최적화 :
간단히 말해서, JVM의 MoniterEnter 및 MonitoreXit Bytecode는 기본 운영 체제의 뮤트 락에 의존하여이를 구현합니다. 그러나 MutxLock을 사용하려면 현재 스레드를 중단하고 사용자 상태에서 커널 상태로 전환하여 실행해야 하므로이 스위칭은 매우 비쌉니다. 그러나 실제로 대부분의 경우 동기화 방법은 단일 스레드 환경 (잠금식 경쟁 환경)에서 실행됩니다. MutxLock이 매번 호출되면 프로그램의 성능에 심각한 영향을 미칩니다. 그러나 JDK1.6에서 자물쇠 거친, 잠금 제거, 경량 잠금, 바이어스 잠금, 적응 형 스피닝 및 기타 기술과 같은 잠금 장치 구현에 대한 많은 최적화가 도입되어 잠금 작동의 오버 헤드를 줄입니다.
잠금 코아 닝 : 즉, 불필요한 잠금 및 잠금 작업을 줄이고, 여러 연속 잠금 장치를 넓은 범위의 잠금으로 확장합니다.
잠금 제거 : 런타임 JIT 컴파일러의 탈출 분석을 통해 일부 잠금 보호가 제거됩니다. 현재 동기화 블록 외부의 다른 스레드에서 공유하지 않는 일부 데이터. 탈출 분석을 통해 객체 공간은 스레드 로컬 스택에 할당 될 수 있습니다 (동시에 힙의 쓰레기 수집 오버 헤드를 줄일 수도 있습니다).
Lightweidentlocking :이 잠금의 구현은 실제 경우 우리 프로그램의 동기화 코드의 대부분이 일반적으로 잠금없는 경쟁 상태 (즉, 단일 스레드 실행 환경)에 있다는 가정에 기초합니다. 자물쇠가없는 경쟁의 경우 운영 체제 수준에서 헤비급 뮤텍스를 완전히 피할 수 있습니다. 대신, MonitorEnter 및 MonitorexIt에서, 당신은 잠금의 획득 및 릴리스를 완료하기 위해 CAS 원자 명령에 의존하면됩니다. 잠금 경쟁이 있으면 CAS 명령을 실행하지 못하는 스레드는 운영 체제 MUTEX를 호출하여 차단 상태를 입력하고 잠금이 해제 될 때 깨어납니다 (특정 처리 단계는 아래에서 자세히 설명합니다).
BIASEDLOCKING : Lock-Free 경쟁의 경우 자물쇠 획득 중에 불필요한 CAS 원자 지침을 실행하는 것을 피하는 것입니다. CAS 원자 지침은 헤비급 잠금에 비해 비용이 상대적으로 적기 때문에 여전히 현지 지연이 매우 상당히 많기 때문입니다 (이 기사 참조).
적응 형 스피닝 : 경량 잠금 장치를 획득하는 동안 스레드가 CAS 작동을 수행하지 않으면 모니터와 관련된 운영 체제 헤비급 잠금 (MUTEXSEMAPHORE)에 들어가기 전에 바쁜 대기에 들어가고 다시 시도합니다. 일정 수의 시도 후에도 여전히 실패하면 모니터와 관련된 세마포어 (즉, 뮤트 잠금)가 차단 상태로 들어가도록 호출됩니다.
3. objectheader :
JVM에서 객체를 만들 때 객체 앞에 두 개의 단어 크기의 객체 헤더가 추가됩니다. 32 비트 기계의 한 단어는 32 비트입니다. 다른 내용은 다른 상태 비트에 따라 Markworld에 저장됩니다. 위 그림에서 볼 수 있듯이 가벼운 잠금 장치에서 Markword는 두 부분으로 나뉩니다. 처음에는 Lockword가 해시 코드로 설정되며 가장 낮은 3 비트는 Lockword가있는 상태를 나타냅니다. 초기 상태는 001이며, 이는 잠금 상태를 의미합니다. Klassptr는 클래스 바이트 코드가 가상 시스템 내부에있는 객체로 표시된 주소를 가리 킵니다. 필드는 연속 객체 인스턴스 필드를 나타냅니다.
4. 모니터 레코드 :
MonitorRecord는 스레드의 개인 데이터 구조입니다. 각 스레드에는 사용 가능한 모니터 레코드 목록과 전역 사용 가능한 목록이 있습니다. 그렇다면이 모니터 레코드의 사용은 무엇입니까? 각 잠긴 객체는 모니터 리드와 관련됩니다 (객체 헤더의 Lockword는 MoniterRecord의 시작 주소를 가리 킵니다.이 주소는 8bete 정렬되었으므로 최저 3 비트의 Lockword는 상태 비트로 사용할 수 있습니다). 동시에, MonitorRecord에 소유자 필드가있어 잠금 장치를 소유 한 스레드의 고유 식별자를 저장하여 잠금 이이 스레드에 의해 점유되었음을 나타냅니다. 다음 그림은 MonitorRecord의 내부 구조를 보여줍니다.
소유자 : 처음에 NULL은 현재 스레드가 모니터 레코드를 소유하고 있음을 의미합니다. 스레드가 잠금을 성공적으로 소유하면 스레드의 고유 한 신원을 저장하고 잠금이 해제되면 NULL로 설정됩니다.
EntryQ : System Mutx (Semaphore)를 연결하여 모니터 레코드를 잠그지 못한 모든 스레드를 차단합니다.
RCTHIS : 모니터 레코드에서 차단되거나 대기하는 모든 스레드의 수를 나타냅니다.
둥지 : 재입국 잠금 장치 계산에 사용됩니다.
해시 코드 : 객체 헤더에서 복사 된 해시 코드 값을 저장합니다 (GC 연령도 포함 할 수도 있음).
후보 : 불필요한 차단을 피하거나 실이 깨어날 때까지 기다리는 데 사용됩니다. 하나의 스레드만이 매번 잠금을 성공적으로 소유 할 수 있기 때문입니다. 잠금을 방출하는 이전 스레드가 매번 모든 차단 또는 대기 스레드를 깨우면 불필요한 컨텍스트 전환 (차단에서 준비로, 경쟁 잠금 고장으로 인해 다시 차단)이 발생하여 심각한 성능 저하로 이어집니다. 후보자는 두 가지 가능한 값 만 있습니다. 0은 자물쇠를 놓고 경쟁하기 위해 후속 스레드를 깨우기 위해 최대 1 개의 수단을 깨울 필요가 없다는 것을 의미합니다.
5. 경량 잠금의 특정 구현 :
스레드는 객체를 두 가지 방법으로 잠글 수 있습니다. 1. 잠금 상태에서 객체를 확장하여 물체의 잠금을 얻습니다 (상태 비트 001). 2. 객체는 이미 확장 된 상태 (상태 비트 00)에 있지만 Lockword가 가리키는 모니터 레코드의 소유자 필드는 NULL이므로 CAS 원자 명령을 통해 소유자를 자체 ID로 직접 설정하여 잠금을 얻을 수 있습니다.
잠금 장치 (모니터 렌터)를 얻는 일반적인 과정은 다음과 같습니다.
(1) 객체가 잠금 상태 일 때 (레코드 값은 해시 코드이고, 상태 비트는 001), 먼저 사용 가능한 모니터 레코드 목록에서 무료 모니터 레코드를 얻습니다. 초기 둥지 및 소유자 값은 각각 1로 사전 설정되고 스레드 자체 식별. 모니터 레코드가 준비되면 CAS 원자체 명령어를 통해 객체 헤더의 잠금 워드 필드에 모니터 레코드의 시작 주소를 확장합니다 (원래 텍스트는 팽창 된 이유는 팽창 된 이유는 주로 확장 된 후에 객체가 확장되기 때문에 주로 객체의 크기를 확장하는 데 사용되며, 레코드 구조가 추출되어 있어야합니다. 이 백서와 모순됩니다. 두 개의 구현 방법은 다른 스레드가 잠금을 위해 경쟁하고 CAS에 대한 호출이 실패하면 Moniterenter로 돌아 가기 만하면됩니다.
(2) 객체가 확장되었고 소유자에 저장된 실은 잠금 자체를 얻는 스레드로 식별됩니다. 이것은 재진입 자물쇠의 경우입니다. 단순히 둥지에 1을 추가하면됩니다. 원자 작동이 필요하고 매우 효율적이지 않습니다.
(3) 객체가 확장되었지만 소유자 값은 NULL입니다. 이 상태는 스레드 차단 또는 잠금 장치 대기가 동시에 잠겨있을 때 발생하며, 잠금의 이전 소유자가 잠금을 방금 해제했습니다. 현재 여러 스레드는 CAS 원자 명령을 통해 소유자를 자신의 정체성으로 설정하여 다중 스레드 경쟁 상태에서 잠금을 얻으려고합니다. 경쟁하지 못한 스레드는 네 번째 사례 (4)의 실행 경로에 들어갑니다.
(4) 객체는 확장 된 상태에 있으며 소유자는 무효가되지 않습니다 (잠금). 운영 체제의 헤비급 뮤텍스를 호출하기 전에 일정 횟수의 회전을합니다. 일정 시간에 도달하면 잠금이 아직 성공적으로 얻어지지 않으면 차단 상태로 들어가기 시작할 때입니다. 먼저, RF이 값을 원자 적으로 1만큼 추가하십시오. 다른 스레드는 1을 추가하는 동안 객체와 모니터 레코드 사이의 관계를 파괴 할 수 있으므로 1을 추가 한 후 다른 비교를 수행해야합니다. 변경되었다는 것이 발견되면 모니터 테너 프로세스를 반복해야합니다. 동시에 소유자가 무효인지 여부는 다시 관찰됩니다. 그렇다면 CAS가 경쟁 잠금에 참여하도록합니다. 잠금 경쟁이 실패하면 차단 상태로 들어갑니다.
잠금 장치를 공개하는 일반적인 과정 (monitorexit)은 다음과 같습니다.
(1) 먼저 객체가 확장 된 상태에 있는지, 실이 잠금의 소유자인지 확인하십시오. 그것이 잘못 되었다면 예외가 발생합니다.
(2) 둥지 필드가 1보다 큰지 확인하십시오. 1보다 큰 경우 둥지를 1으로 줄이고 잠금을 계속하십시오. 1과 같으면 단계 (3)을 입력하십시오.
(3) RFTHIS가 0보다 큰지 확인하고 소유자를 NULL로 설정하고 차단 또는 대기 스레드를 깨우기 위해 잠금을 다시 얻으려고합니다. 0과 같으면 단계 (4)에 들어갑니다.
(4) 객체를 디플레이션하고, 객체의 잠금 워드를 원래 해시 코드 값으로 다시 교체하여 잠금을 해제하여 잠금을 해제하여 잠금을 해제하고 모니터 레코드를 스레드에 다시 넣습니다.
요약
참조 : " Java Virtual Machine JVM (Zhou Zhiming)의 고급 기능 및 모범 사례에 대한 심층적 인 이해 "
위는 JVM 세부 사항의 동기화 및 구현 문제 분석에 대한이 기사의 전체 내용입니다. 모든 사람에게 도움이되기를 바랍니다. 단점이 있으면 메시지를 남겨 두십시오. 이 사이트를 지원해 주신 친구들에게 감사드립니다!