JMM은 Java에 대해 더 깊은 이해를 원하는 프로그래머에게는 피할 수 없는 수준입니다. 이 글은 좀 더 이론적이고 이해하기 쉬운 내용이므로, 혹시라도 실수가 있으면 바로잡아 주셨으면 합니다.
그럼 먼저 jvm의 메인 메모리 할당에 대해 이야기해보겠습니다.
1개의 Java 가상 머신 스택(java virtual stack) )
가상 머신 스택은 스레드 전용입니다. 각 스레드에는 각 메소드가 실행될 때 Java 메소드 실행을 위한 메모리 모델 인 자체 가상 머신 스택이 있습니다. , 가상 머신 스택에 스택 프레임을 생성합니다. 스택 프레임은 주로 로컬 변수 (기본 유형, 개체 의 참조, 반환)를 저장하는 데이터 구조입니다. 주소 유형(바이트코드 명령어의 주소를 가리킴)), 연산 스택(메소드 컴파일 후 연산 명령어의 스택을 참조), 동적 연결, 메소드 종료. 일반적으로 자바 메모리는 스택(Stack)과 힙(Heap)으로 구분되는데, 스택은 가상머신 스택을 말한다. 그러나 Java의 메모리 할당은 그렇게 간단하지 않습니다.
동적 연결은 다음과 같이 설명됩니다.
각 스택 프레임에는 풀에서 스택 프레임이 속한 메서드에 대한 실행 런타임 상수 참조가 포함되어 있습니다. 메소드 호출 중 동적 연결을 지원하기 위해 개최되었습니다.
Class 파일에는 많은 수의 기호 참조가 저장됩니다. 바이트코드의 메서드 호출 명령어는 상수 풀의 메서드를 가리키는 기호 참조를 매개변수로 사용합니다. 이러한 기호 참조 중 일부는 클래스 로딩 단계에서 또는 처음으로 사용될 때 직접 참조로 변환됩니다. 이 변환을 정적 구문 분석 이라고 합니다. 다른 부분은 각 실행 중에 직접 참조로 변환되는데, 이 부분을 동적 링크라고 합니다.
메소드 종료에 대한 설명은 다음과 같습니다.
실행이 반환 명령을 만나면 반환 값이 상위 메소드 호출자에게 전달됩니다. 일반 완료 종료(Normal Method Invocation Completion)라고 합니다. 일반적으로 호출자의 PC 카운터를 반환 주소로 사용할 수 있습니다.
실행 중 예외가 발생하여 현재 메서드 본문이 처리되지 않으면 메서드가 종료됩니다. 이때 반환 값이 없는 것을 Abrupt라고 합니다. 메소드 호출 완료), 반환 주소는 예외 처리기 테이블을 통해 결정되어야 합니다.
가상 머신 스택에는 두 가지 예외가 있습니다. 하나는 일반적인 OOM이고 다른 하나는 StackOverFlowError입니다. StackOverflowError는 일반적으로 재귀 호출로 인해 발생합니다. 가상 머신에서는 스택 깊이도 제한됩니다. 그렇지 않으면 무제한 재귀 호출인 경우 가상 머신이 울립니다. 말할 필요도 없이, 요청된 메모리가 현재 가상 머신 스택이 보유하고 있는 메모리보다 클 때 OOM이 발생합니다. (가상 머신 스택 공간은 동적으로 확장할 수 있지만 jvm에 할당된 메모리도 제한되어 있으므로 가상 머신 스택은 무한히 확장 가능하지 않음).
2 로컬 메소드 스택
로컬 메소드 스택과 가상 머신 스택은 기본적으로 유사하지만, 가상 머신 스택은 클래스 바이트코드를 실행하는 반면, 로컬 메소드 스택은 메소드 스택에서 실행되는 것은 로컬 메소드의 서비스입니다. 이는 실제로 서로 다른 OS 플랫폼에 따라 C 또는 C++로 작성된 동일한 메소드의 일부 다른 구현을 호출합니다.
3 메소드 영역
메소드 영역은 스레드들이 공유하는 영역으로 가상 머신( 클래스)에서 로딩한 클래스 정보를 저장하는데 사용된다. 바이트코드 데이터의 경우 동시에 많은 클래스를 로드하는 경우 메소드 영역의 공간을 늘려야 한다는 점에 유의해야 합니다. 그렇지 않으면 OOM이 발생합니다. 이는 클래스가 거의 없는 경우에만 수행할 수 있습니다. 클래스가 너무 많으면 스프링의 지연 로딩 메커니즘과 같은 지연 로딩을 사용할 수 있습니다. 너무 많은 클래스(🎜>), 상수, 정적 변수 및 JIT(Just-In-Time 컴파일러)로 컴파일된 코드를 로딩하지 마세요. 동시에 다른 데이터. 메소드 영역은 실제로 우리가 영구 생성 영역이라고 부르는 것입니다(는 핫스팟 가상 머신의 구현 메커니즘으로 제한됩니다). 영구 생성이라고 불리는 이유는 여기의 데이터가 거의 가비지 수집되지 않기 때문입니다. 로딩으로 인해 클래스는 짧은 시간 내에 사라지지 않습니다. 많은 메소드는 클래스를 기반으로 힙에 객체를 생성하지만 정적 변수는 일반적으로 gc 및 검색 알고리즘의 루트 노드입니다. . 데이터가 거의 정리되지 않습니다. Java Virtual Machine 사양도 이 영역에 대한 제한이 매우 느슨하며 물리적으로 불연속적인 공간일 뿐만 아니라 고정된 크기와 확장성을 허용하며 가비지 수집을 구현할 필요가 없습니다. 상대적으로 말하면 이 영역에서는 가비지 수집 동작이 비교적 드뭅니다. 따라서 상수 및 정적 변수의 정의에 더 많은 주의를 기울여야 합니다. 메서드 영역의 메모리 수집은 계속 발생하지만 이 영역의 메모리 수집은 주로 상수 풀 재활용 및 유형 언로드를 위한 것입니다. 일반적으로 메소드 영역에서 메모리 재활용은 만족하기 어렵습니다. 메서드 영역이 메모리 할당 요구 사항을 충족할 수 없으면 OutOfMemoryError 예외가 발생합니다. 4 런타임 상수 풀 JDK1.6 이전String상수 풀은 메소드 영역에 위치합니다. Java는 코드에 정의된 다양한 기본 유형(예: int) 외에도 상수 풀의 역할이 매우 중요합니다. , long 등) 및 개체 유형(예: String 및 Array)에는 다음과 같은 텍스트 형식의 일부 기호 참조도 포함됩니다. 클래스 및 인터페이스 필드 이름 및 설명자 메서드 및 이름 및 설명자. C 언어에서 프로그램이 다른 라이브러리의 함수를 호출하려는 경우 연결할 때 라이브러리에서 함수의 위치(즉, 상대 라이브러리에 파일 시작 부분의 오프셋이 프로그램에 기록됩니다. 런타임 시 함수는 이 주소에서 직접 호출됩니다. Java 언어에서는 모든 것이 동적입니다. 컴파일 시 다른 클래스 메소드에 대한 호출이나 다른 클래스 필드에 대한 참조가 발견되면 클래스 파일에 기록되는 내용은 연결 과정에서 이 텍스트를 기반으로 검색합니다. 정보입니다. 해당 방법 또는 필드입니다. 따라서 Java 언어의 소위 "상수"와 달리 클래스 파일의 "상수" 내용은 클래스 내 한 영역에 차례대로 집중되어 있습니다. , 여기서는 "상수 풀"이라고 합니다. Java의 상수 풀 기술은 특정 개체를 편리하고 빠르게 생성할 수 있는 것으로 나타납니다. 개체가 필요할 때 풀에서 꺼낼 수 있습니다(풀에 개체가 없으면 새로 생성). 동일한 변수를 반복적으로 생성해야 할 때 많은 시간을 절약할 수 있습니다. 상수 풀은 실제로 new 키워드를 사용하여 생성된 객체가 위치한 힙 공간과 다른 메모리 공간입니다. 전체 상수 풀은 JVM의 인덱스에 의해 참조됩니다. 배열의 요소 컬렉션이 인덱스에 따라 액세스되는 것처럼 JVM도 이러한 상수 풀에 저장된 정보를 처리합니다. 실제로, 상수 풀은 Java 프로그램에서 동적 링크 프로세스 가 중요한 역할을 합니다(위에서 언급한 내용). 다음은 "Java Virtual Machine에 대한 심층적인 이해"에서 발췌한 것입니다. 클래스 버전, 필드, 메소드, 인터페이스 및 기타 정보 외에도 컴파일러에서 생성된 다양한 리터럴 및 기호 참조를 저장하는 데 상수 풀이 사용된다는 정보가 클래스 파일에 있습니다. 정보는 클래스가 로드된 후 메소드 영역의 런타임 상수 풀에 저장됩니다. Java 가상 머신은 클래스의 모든 부분(상수 풀 포함)에 대해 엄격한 규정을 가지고 있으며, 각 바이트를 저장하는 데 사용되는 데이터의 종류에는 사양 요구 사항이 있어야 가상 머신에서 인식하고 로드하고 실행할 수 있습니다. 일반적으로 클래스 파일에 설명된 기호 참조를 저장하는 것 외에도 번역된 직접 참조도 런타임 상수 풀에 저장됩니다. 클래스 파일 상수 풀과 비교하여 런타임 상수 풀의 또 다른 중요한 특징은 동적이라는 것입니다. Java 가상 머신에서는 상수가 컴파일 중에만 생성될 수 있도록 요구하지 않습니다. 즉, 사전에 생성되지 않습니다. - 클래스 파일 상수 풀에 내장되어 있으며, 실행 중에도 새로운 상수를 상수 풀에 넣을 수 있습니다. 상수 풀은 메서드 영역의 일부이므로 메모리에 의해 제한됩니다. 충분한 메모리를 적용할 수 없으면 OutOfMemoryError 예외가 발생합니다. 5 힙 힙은 메모리에서 가장 큰 영역이며 객체 인스턴스를 저장하는 데 사용되는 유일한 장소입니다. 이곳은 GC 알고리즘의 주요 전장이기도 하다. 그러나 JIT(Just In Time Compilation)의 발전과 이스케이프 기술의 성숙으로 인해 모든 객체가 힙에 생성되는 것은 아닙니다. 다음은 "Java Virtual Machine의 심층적 이해"에서 발췌한 것입니다. Java 프로그래밍 언어 및 환경에서 Just-In-time 컴파일러(JIT 컴파일러)는 Java 바이트코드(해석이 필요한 명령어 포함)를 Java 바이트코드로 변환하는 프로그램입니다. 프로세서에 직접 보낼 수 있는 명령입니다. Java 프로그램을 작성할 때 소스 언어 명령문은 특정 프로세서 하드웨어 플랫폼(예: Intel의 Pentium 마이크로프로세서 또는 IBM의 System/390 프로세서)에 해당하는 명령 코드로 컴파일되지 않고 Java 컴파일러에 의해 바이트코드로 컴파일됩니다. 바이트코드는 모든 플랫폼으로 전송되고 해당 플랫폼에서 실행될 수 있는 플랫폼 독립적인 코드입니다. Java의 메모리 할당은 대략 이렇습니다. jvm에도 위의 데이터를 조정할 수 있는 많은 매개변수가 있습니다. 여기에는 나열하지 않고 별도의 gc 관련 글에서 자세히 설명하겠습니다. 멀티 코어 프로세서 시대에 동시성으로 인해 발생하는 문제를 JVM이 어떻게 처리하는지 이야기해 보겠습니다. 동시성 제어 멀티 코어 CPU는 여러 스레드를 동시에 실행할 수 있으며, 각 스레드는 자체 로컬 작업 공간(실제로는 위의 메인 메모리에서 얻은 데이터를 작업 공간에서 실행하기 위해 복사본으로 저장하는 시스템 캐시 및 각 코어에 할당된 레지스터입니다. 공유 변수의 데이터 불일치 문제와 관련된 데이터 교환을 수행할 수 없습니다. Java는 동기화된 휘발성 잠금과 같은 메커니즘을 통해 공유 변수의 가시성을 제어합니다. 동기화 및 잠금은 각각 구현 메커니즘을 설명하기 위해 별도의 장으로 구성됩니다. 말할 필요도 없이 이 두 가지는 가시성과 원자성 측면에서 보장됩니다. Volatile은 데이터의 가시성을 보장할 뿐입니다. 데이터를 읽고 로드할 때만 다른 스레드의 데이터 변경 사항이 현재 스레드에서 감지됩니다. 이 두 단계를 통과하면 데이터 불일치(실제로 휘발성이 하는 일은 캐시 사용을 피하고 메인 메모리의 데이터를 스레드 작업 메모리에 저장하지 않는 것입니다. 읽기 및 로드 단계에서는 다른 스레드가 감지할 수 있도록 메인 메모리에서 데이터를 가져옵니다. 변수 수정 ). Volatile은 초기 jdk 버전에서 동기화 성능이 좋지 않아 가시성을 확보하기 위한 솔루션일 뿐입니다. 현재 jdk 버전의 동기화 및 잠금은 어느 정도 최적화되었으므로 동시성의 정확성을 보장하지 않기 때문에 현재 휘발성을 사용하고 있는 것이 무엇인지 알지 않는 한 일반적으로 휘발성 변수를 사용하지 않는 것이 좋습니다. 메인 메모리에서 현재 작업 메모리로 변수 복사 읽기 및 로드, 실행 코드 사용 및 할당, 공유 변수 값 변경, 작업 메모리 사용 데이터 저장 및 쓰기를 통해 메인 메모리 새로 고침 사용 및 할당이 여러 번 나타날 수 있는 메모리 관련 콘텐츠입니다. 휘발성은 일부 멱등성 작업에 적합합니다. 이는 잠금의 nofairsync 구현에서 설명됩니다. 이 시점에서 이전 발생의 원리에 대해 이야기해야 합니다. 이는 Java 메모리 모델에 정의된 두 작업 간의 부분 순서 관계입니다. A 작업이 B 작업보다 먼저 발생하면 B 작업이 발생하기 전에 A 작업의 영향을 B 작업으로 관찰할 수 있습니다. "영향"에는 수정이 포함됩니다. 메모리에 있는 공유 변수의 값, 메시지 보내기, 메서드 호출 등. 기본적으로 시간에 따른 일련의 이벤트와는 거의 관련이 없습니다. 이 원칙은 데이터에 경쟁이 있는지, 스레드가 안전한지 판단하는 주요 기준입니다. 다음은 사전 발생을 보장하는 Java 메모리 모델의 8가지 규칙입니다. 이 규칙은 동기화 장치의 지원 없이 이미 존재하며 코딩에 직접 사용할 수 있습니다. 두 작업 간의 관계가 이 목록에 없고 다음 규칙에서 추론할 수 없는 경우 순서가 보장되지 않으며 가상 머신이 임의로 순서를 변경할 수 있습니다(CPU를 완전히 활용하고 활용도를 높이기 위해 jvm, JVM). 관련 없는 코드나 작업을 재정렬하여 IO나 다른 리소스를 기다려야 하는 작업은 뒤에 배치하고, 즉시 완료할 수 있는 다른 작업은 앞에 배치하여 먼저 실행하여 CPU 리소스를 최대한 활용합니다. 1. 프로그램 순서 규칙: 별도의 스레드에서 프로그램 코드의 실행 흐름 순서에 따라 먼저(시간에) 수행된 작업이 나중에 수행된 작업보다 먼저(시간에) 발생합니다. 2. 관리 잠금 규칙: 잠금 해제 작업은 동일한 잠금에 대한 잠금 작업 이전에(시간 순서대로 아래 동일) 발생합니다. 3. 휘발성변수 규칙: 휘발성 변수에 대한 쓰기 작업은 해당 변수에 대한 후속 읽기 작업 전에 발생합니다. 4. 스레드 시작 규칙: Thread 개체의 start() 메서드는 이 스레드의 모든 작업 전에 발생합니다. 5. 스레드 종료 규칙: 스레드의 모든 작업이 발생합니다. 이 스레드의 종료가 감지되기 전에 스레드의 반환 값인 Thread.join() 메서드의 끝을 통해 스레드를 감지할 수 있습니다. isAlive() 등 실행이 종료되었습니다. 6. 스레드 중단 규칙: 스레드 Interrupt() 메서드에 대한 호출은 중단된 스레드의 코드가 중단을 감지할 때 이벤트 가 발생하기 전에 발생합니다. 7. 객체 마무리 규칙: 객체의 초기화는 finalize() 메서드가 시작되기 전에 완료됩니다( 생성자 실행 종료). 8. 이행성: 작업 A가 작업 B 전에 발생하고 작업 B가 작업 C 전에 발생하면 작업 C보다 먼저 A가 발생한다고 결론을 내릴 수 있습니다.
JDK1.7 문자열 상수 풀이 힙으로 이동되었습니다.
위 내용은 JMM Java 메모리 모델에 대한 자세한 그래픽 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!