JVM 메모리 이해

JVM의 메모리 레이아웃에 대해 이야기합니까?
JVM 메모리 이해

Java 가상 머신에는 주로 다음과 같은 여러 영역이 포함됩니다.

힙 : 힙 JVM (Java Virtual Machine)에서 가장 큰 메모리는 스레드가 공유하는 메모리 영역이며 기본적으로 모든 객체 인스턴스 배열은 힙에 할당 된 공간입니다. 힙 영역은 Yound 영역의 Young 세대와 Old 영역의 Old Generation으로 세분되며, Young 세대는 Eden, S0, S1의 세 부분으로 나뉘며 기본 비율은 8 : 1 : 1입니다.

스택 : 스택은 스레드 전용 메모리 영역입니다. 각 메소드가 실행될 때 스택에 스택 프레임이 생성됩니다. 메소드의 호출 프로세스는 스택을 스택 및 팝하는 프로세스에 해당합니다. 각 스택 프레임의 구조에는 로컬 변수 테이블, 피연산자 스택, 동적 연결, 메서드 반환 주소가 포함됩니다.

로컬 변수 테이블은 메서드 매개 변수와 로컬 변수를 저장하는 데 사용됩니다. 첫 번째 메소드가 호출되면 매개 변수가 0부터 시작하는 연속 지역 변수 테이블로 전달됩니다.

피연산자 스택은 로컬 변수 테이블에서 피연산자 스택으로 일부 바이트 코드 명령을 전송하는 데 사용되며 메서드 호출 매개 변수를 준비하고 메서드 반환 결과를 수신하는데도 사용됩니다.

동적 연결은 기호 참조로 표시되는 메소드를 실제 메소드의 직접 참조로 변환하는 데 사용됩니다.

메타 데이터 : Java 1.7 이전에는 메소드 영역 개념이 포함되었습니다. 상수 풀은 메소드 영역 (영구 생성)에 존재했고 메소드 영역 자체는 논리적 개념이었습니다. 1.7 이후에는 상수 풀이 힙으로 이동되었습니다. 1.8 이후에는 영구 생성 개념이 제거되고 (메소드 영역의 개념은 여전히 ​​유지됨) 구현 방법이 현재 메타 데이터입니다. 클래스 및 런타임 상수 풀의 메타 정보를 포함합니다.

클래스 파일은 클래스와 인터페이스의 정의 정보입니다.

런타임 상수 풀은 클래스 및 인터페이스의 상수 풀의 런타임 표현입니다.

네이티브 메서드 스택 : 네이티브 네이티브 메서드를 실행하는 데 주로 사용되는 영역

프로그램 카운터 : 현재 스레드에서 가상 머신이 실행중인 바이트 코드의 명령어 주소를 기록하는 데 사용되는 스레드 전용 영역이기도합니다.

개체를 새로 만드는 과정을 알고 있습니까?
JVM 메모리 이해

가상 기회가 새로운 키워드를 볼 때 실현은 현재 클래스가로드되었는지 여부를 판단하고, 클래스가로드되지 않은 경우 먼저 클래스의로드 메커니즘을 실행 한 다음 공간을 할당하고로드가 완료된 후 객체를 초기화합니다.

먼저 현재 클래스가로드되었는지 확인하고로드되지 않은 경우 클래스로드 메커니즘을 실행합니다.

로딩 : 바이트 코드에서 바이너리 스트림으로 로딩하는 과정입니다.

검증 : 물론로드가 완료된 후 클래스 파일이 가상 머신 사양을 충족하는지 확인해야합니다. 인터페이스 요청과 마찬가지로 먼저 매개 변수 검증을 수행해야합니다.

준비 : 정적 변수 및 상수에 기본값 할당

해결 방법 : 상수 풀에서 기호 참조를 대체하는 프로세스 (참조 된 대상을 설명하는 기호 사용)를 참조 (대상에 대한 포인터 또는 핸들 등)로 변경합니다.

초기화 : 정적 코드 블록 (cinit)을 실행하여 초기화하고, 부모 클래스가있는 경우 부모 클래스를 먼저 초기화합니다.

추신 : 정적 코드 블록은 절대적으로 스레드로부터 안전하며 클래스로드 프로세스 중에 Java 가상 머신에 의해서만 암시 적으로 초기화되고 호출 될 수 있습니다! (여기에 문제가 있습니까, 정적 코드 블록 스레드가 안전한가요?)

클래스가로드되면 객체 할당 및 초기화 과정을 따릅니다.

먼저 개체에 적합한 크기의 메모리 공간을 할당합니다.

그런 다음 인스턴스 변수에 기본값을 할당하십시오.

객체 헤더 정보, 객체 해시 코드, GC 생성 연령, 메타 데이터 정보 등을 설정합니다.

생성자 (초기화) 초기화 수행

부모 위임 모델을 알고 있습니까?
클래스 로더는 위에서 아래로 나뉩니다.

Bootstrap ClassLoader는 클래스 로더를 시작합니다. 기본적으로 JAVA_HOME / lib 디렉토리에 jar를로드합니다.

Extention ClassLoader 확장 클래스 로더 : JAVA_HOME / lib / ext 디렉토리에 jar를로드하는 기본값

애플리케이션 ClassLoader : 예를 들어, 웹 애플리케이션은 웹 프로그램의 ClassPath 아래에 클래스를로드합니다.

사용자 ClassLoader 사용자 정의 클래스 로더 : 사용자가 정의

클래스를로드 할 때 먼저 부모 로더가로드되었는지 묻습니다. 그렇지 않은 경우 차례로 묻습니다.로드되지 않은 경우로드가 성공할 때까지 현재 클래스를 위에서 아래로로드하려고합니다.

JVM 메모리 이해

가비지 수집 알고리즘은 무엇입니까?
Mark-clear
는 재활용 할 개체를 균일하게 표시합니다. 마킹이 완료된 후 모든 표시된 개체는 균일하게 재활용됩니다. 마킹 프로세스는 모든 GC ROOT를 통과해야하기 때문에 지우기 프로세스도 힙의 모든 개체를 통과하므로 mark-clear 이 알고리즘은 비효율적이며 메모리 조각화 문제도 발생합니다.

복사 알고리즘
성능 문제를 해결하기 위해 복사 알고리즘이 생겨 났는데, 메모리를 같은 크기의 두 영역으로 나누어 매번 하나씩 사용하고, 하나의 메모리가 다 사용되면 남은 객체를 다른 메모리로 복사합니다. 이 영역에서 현재 메모리가 지워 지므로 성능 및 메모리 조각화 문제를 해결할 수 있습니다. 그러나 또 다른 문제가 발생합니다. 사용 가능한 메모리 공간이 절반으로 줄어 듭니다!

따라서 우리의 현재 공통 젊은 세대 + 구세대 메모리 구조가 탄생했습니다 : Eden + S0 + S1, IBM 연구에 따르면 개체의 98 %가 살아 있고 죽어 가고 있기 때문에 실제 살아남은 개체는 그렇지 않습니다. 메모리 낭비가 많으므로 기본 비율은 8 : 1 : 1입니다.

이런 식으로 사용 중일 때는 Eden 영역과 S0S1 중 하나만 사용하고 남은 개체는 매번 사용되지 않은 다른 Survivor 영역에 복사되고 Eden과 사용 된 Survivor는 동시에 지워져 메모리 낭비가 10 %에 불과합니다. .

마지막으로 사용하지 않은 생존자가 살아남은 물체를 내려 놓을 수 없다면,이 물체는 노년기에 들어갑니다.

추신 : 그렇다면 왜 당신이 Eden 구역과 2 개의 Survior 구역으로 나뉘 었는지 묻는 몇 가지 기본적인 질문이 있습니까? 효과는 무엇입니까? 메모리를 절약하고 메모리 단편화 문제를 해결하기위한 것으로, 이러한 알고리즘은 문제를 해결하기 위해 생성 된 것으로, 이유를 이해하면 암기 할 필요가 없습니다.

마킹과 정렬
은 노후에 진입 한 객체의 생존율이 상대적으로 높기 때문에 복제 알고리즘을 사용하는 것은 분명히 노후에 부적절합니다. 이때 잦은 복제는 성능에 더 큰 영향을 미치며 처리를위한 추가 공간이 없습니다. 모든 세부 사항을 공개하십시오. 따라서 노년기의 특성에 따라 마크 구성 알고리즘을 통해 모든 생존 오브젝트가 마킹되어 모든 생존 오브젝트가 한쪽 끝으로 이동 한 후 경계 외부의 메모리 공간이 정리됩니다.

그렇다면 GC ROOT은 무엇입니까? GC ROOT는 무엇입니까?
위에서 언급 한 마킹 알고리즘, 개체가 살아 있는지 여부를 표시하는 방법은 무엇입니까? 참조 카운팅 방법을 사용하여 객체에 참조 카운터를 설정하기 만하면 참조가있을 때마다 카운터는 +1이고 그렇지 않은 경우 카운터는 -1이지만이 간단한 알고리즘은 순환 참조 문제를 해결할 수 없습니다.

Java는 도달 가능성 분석 알고리즘을 사용하여 생존 객체를 표시하는 목적을 달성합니다. 일련의 GC ROOT를 시작점으로 정의하고 시작점에서 아래쪽으로 검색합니다. 검색 경로를 참조 체인이라고합니다. 객체가 GC ROOT에 도달하면 아무것도 없습니다. 참조 체인이 연결되어 있으면 개체를 재활용 할 것인지 결정할 수 있습니다.

GC ROOT로 사용할 수있는 개체는 다음과 같습니다.

스택에서 참조되는 개체

정적 변수 및 상수가 참조하는 객체

네이티브 메서드 스택의 네이티브 메서드에서 참조하는 개체

가비지 수집기가 이해합니까? 젊은 세대와 노인 세대를 위해 어떤 쓰레기 수집가가 있습니까?
JVM 메모리 이해

젊은 세대의 가비지 수집기에는 Serial, ParNew 및 Parallell이 포함되고 이전 세대에는 Serial Old, CMS, Parallel Old 및 JDK11의 새로운 G1 수집기가 포함됩니다.

직렬 : 수집기의 단일 스레드 버전 인 STW (Stop The World)는 가비지 수집 중에 사용됩니다. 즉, 다른 작업자 스레드는 가비지 수집 중에 일시 중단되어야합니다.

ParNew : CMS와 함께 사용되는 Serial의 다중 스레드 버전

병렬 청소 : 병렬로 수집 할 수있는 다중 스레드 가비지 수집기

Serial Old : 단일 스레드 인 Serial의 이전 버전

Parallel Old : Parallel Scavenge의 이전 버전

CMS (Concurrent Mark Sweep) : CMS 수집기는 가장 짧은 일시 중지 시간을 확보하는 것을 목표로하는 수집기입니다. 다른 수집기에 비해 STW 시간이 더 짧습니다. 병렬 수집이 가능하며 표시 제거 알고리즘을 기반으로합니다. 전체 GC 프로세스는 4 단계로 나뉩니다.

초기 표시 : GC ROOT가 연관 될 수있는 오브젝트를 표시하십시오. STW는 필수입니다.

동시 마킹 : STW없이 GCRoots의 직접 연결된 개체에서 전체 개체 그래프를 횡단하는 프로세스

비고 : 동시 마킹 중 사용자 프로그램의 지속적인 동작으로 인해 변경되는 마킹을 수정하기 위해서는 STW가 필요합니다.

동시 정리 : 마킹 단계에서 판단 된 죽은 개체를 정리하고 삭제합니다. STW가 필요하지 않습니다.

전체 프로세스의 관점에서 동시 마킹과 동시 클리어링은 시간이 가장 오래 걸리지 만 사용자 스레드를 중지 할 필요가없고 초기 마킹 및 리마킹에 시간이 덜 걸리지 만 사용자 스레드를 중지해야합니다. 일시 중지 시간은 짧으며 대부분 사용자 스레드에서 작동 할 수 있습니다.

G1 (Garbage First) : G1 수집기는 JDK9의 기본 가비지 수집기이며 더 이상 수집을 위해 젊은 세대와 이전 세대를 구분하지 않습니다.

G1의 원리를 이해하십니까?
JVM 메모리 이해

G1은 JDK9 이후 서버에서 기본 수집기 역할을하여 더 이상 가비지 수집을 위해 젊은 세대와 구세대를 구분하지 않습니다. 메모리를 여러 영역으로 분할하고 각 영역의 크기는 -XX : G1HeapRegionSize로 설정할 수 있습니다. 크기는 1 ~ 32M, 대형 오브젝트 저장을 위해 Humongous 개념이 도출되며, Region 크기의 절반을 초과하는 오브젝트는 대형 오브젝트로 간주되고 전체 Region의 크기를 초과하는 오브젝트는 초대형 오브젝트로 간주되어 연속 N에 저장됩니다. Humongous Region에서 G1은 재활용시 백그라운드에서 우선 순위 목록을 유지하며, 사용자가 설정 한 수거 일시 중지 시간에 따라 수익이 가장 높은 지역이 처음으로 재활용 될 때마다 매번 재활용됩니다.

G1의 재활용 프로세스는 다음 네 단계로 나뉩니다.

초기 표시 : GC ROOT가 연관 될 수있는 오브젝트를 표시하십시오. STW는 필수입니다.

동시 마킹 : GCRoots의 직접 연결된 개체에서 전체 개체 그래프를 순회하는 과정이며, 스캔이 완료된 후 동시 마킹 과정에서 변경된 개체가 다시 처리됩니다.

최종 표시 : 사용자 스레드를 잠시 중단하고 다시 처리합니다. STW가 필요합니다.

선별 및 재활용 : 지역의 통계를 업데이트하고, 각 지역의 재활용 가치와 비용을 정렬하고, 사용자가 설정 한 일시 중지 시간을 기반으로 재활용 계획을 수립합니다. 그런 다음 재활용해야하는 리전에서 남아있는 개체를 빈 리전으로 복사하고 이전 리전을 정리합니다. STW 필요

일반적으로 동시 마킹 외에도 여러 다른 프로세스에는 여전히 짧은 STW가 필요합니다 .G1의 목표는 제어 가능한 일시 중지 및 지연을 통해 처리량을 최대한 늘리는 것입니다.

YGC 및 FGC는 언제 트리거됩니까? 피험자는 언제 노년에 들어 갑니까?
새로운 객체가 메모리 공간에 적용되면 Eden 영역이 메모리 할당 요구 사항을 충족하지 못하면 YGC가 트리거되고 사용중인 Survivor 영역 및 Eden 영역의 라이브 객체는 사용되지 않은 Survivor 영역으로 전송됩니다 .YGC 이후에도 공간이 충분하지 않은 경우 , 그런 다음 이전 세대 할당을 직접 입력하고 이전 세대가 공간을 할당 할 수없는 경우 FGC를 트리거합니다. FGC가 여전히이를 중단 할 수없는 경우 OOM 예외가보고됩니다.

JVM 메모리 이해

YGC 이후에 남아있는 개체는 사용하지 않은 Survivor 영역으로 복사되며 S 영역을 내려 놓을 수없는 경우 구세대로 직접 승격됩니다. Survivor 영역에서 앞뒤로 복사 된 개체의 경우 교환 임계 값은 -XX : MaxTenuringThreshold를 통해 구성되며 기본값은 15 회이며 숫자를 초과하면 이전 연령도 입력됩니다.

또한 MaxTenuringThreshold를 기다리지 않고 구세대를 승격시킬 수있는 동적 연령 판단 메커니즘이 있습니다. 생존자 공간에서 같은 연령대의 모든 물체의 총 크기가 생존자 공간의 절반보다 크면 연령이이 연령보다 크거나 같은 물체가 직접 노년기에 들어갈 수 있습니다.

잦은 FullGC 문제를 해결하는 방법은 무엇입니까?
이런 종류의 문제를 해결하는 가장 좋은 방법은 특정 예를 사용하여 분석하는 것입니다. 그렇지 않은 경우 일반적인 분석 단계에 대해 이야기하십시오. 비합리적인 메모리 할당으로 인해 FGC가 발생할 수 있습니다. 예를 들어 Eden 영역이 너무 작아 개체가 노후화되는 경우가 자주 발생합니다. 이는 시작 매개 변수 구성을 통해 확인할 수 있습니다. 또한 메모리 누수가 발생할 수 있으며 다음 단계를 통해 확인할 수 있습니다.

jstat -gcutil 또는 gc.log 로그를보고 메모리 복구 확인

JVM 메모리 이해
JVM 메모리 이해
S0 및 S1은 각각 두 생존자 영역의 비율을 나타냅니다.

E는 그림에서 볼 수 있듯이 Eden 면적의 비율을 나타냅니다. 78 %가 사용됩니다.

O는 노년기, M은 메타 공간, YGC는 54 회 발생, YGCT는 YGC가 소비하는 누적 시간, GCT는 GC가 소비하는 누적 시간을 나타냅니다.

JVM 메모리 이해

[GC [처음에있는 FGC는 가비지 수집 유형을 나타냅니다.

PSYoungGen : 6130K-> 6130K (9216K)] 12274K-> 14330K (19456K), 0.0034895 초는 YGC 전후의 메모리 사용량을 나타냅니다.

Times : user = 0.02 sys = 0.00, real = 0.00 secs, user는 사용자 모드에서 소비 된 CPU 시간을, sys는 커널 모드에서 소비 된 CPU 시간을, real은 다양한 벽시계의 대기 시간을 나타냅니다.

이 두 수치는 예시 일뿐 상관 관계가 없습니다. 예를 들어, FGC 수행 여부, FGC 소요 시간, GC 이후 젊은 세대의 기억력 감소 여부, 일부 예비 정보 획득 여부 등을 그림에서 확인할 수 있습니다. 심판.

특정 분석을 위해 메모리 파일을 덤프합니다 (예 : jmap 명령 jmap -dump : format = b, file = dumpfile pid 사용), 내 보낸 후 Eclipse Memory Analyzer 및 기타 도구를 통해 분석하여 코드를 찾아 수정합니다.

CPU 급증과 같은 질문이있을 수 있으며 FGC는 어떻습니까? 방법은 비슷합니다

현재 프로세스의 pid 찾기, top -p pid -H 자원 점유보기, 스레드 찾기

printf "% x \ n"pid, 스레드 pid를 0x32d와 같은 16 진수로 변환

jstack pid | grep -A 10 0x32d 스레드의 스택 로그를 보지만 문제가 계속되지 않습니다.

메모리 파일을 덤프하고 MAT 및 기타 도구로 분석하고 코드를 찾아 수정합니다.

JVM 튜닝에 대한 경험이 있습니까?
모든 튜닝의 목적은 더 적은 하드웨어 비용으로 더 높은 처리량을 달성하는 것이며, JVM의 튜닝은 동일하여 가비지 수집기와 메모리 할당을 튜닝하여 최상의 성능을 달성하는 것임을 이해해야합니다.

간단한 매개 변수 의미
먼저 몇 가지 주요 매개 변수의 의미를 알아야합니다.

JVM 메모리 이해

-Xms는 초기 힙 크기를 설정하고 -Xmx는 최대 힙 크기를 설정합니다.

-XX : NewSize 젊은 세대 크기, -XX : MaxNewSize 젊은 세대 최대 값, -Xmn은 -XX : NewSize 및 -XX : MaxNewSize를 동일한 값으로 구성하는 것과 동일합니다.

-XX : NewRatio는 구세대에 대한 젊은 세대의 비율을 설정합니다. 3 인 경우 젊은 세대와 구세대의 비율은 1 : 3이고 기본값은 2입니다.

-XX : SurvivorRatio 젊은 세대와 두 생존자의 비율, 기본값은 8, 비율이 8 : 1 : 1임을 의미합니다.

-XX : PretenureSizeThreshold 생성 된 객체가 지정된 크기를 초과 할 경우 이전 세대의 객체를 직접 할당합니다.

-XX : MaxTenuringThreshold는 Survivor에서 복제 할 개체의 최대 수명 임계 값을 설정하고 임계 값을 초과하는 이전 수명으로 전송합니다.

-XX : MaxDirectMemorySize Direct ByteBuffer가 할당 한 오프 힙 메모리가 지정된 크기에 도달하면 Full GC가 트리거됩니다.

조정
로그를 인쇄하여 문제 해결을 용이하게하려면 GC 로그를 활성화하는 것이 가장 좋습니다. GC 로그를 활성화하면 성능에 미치는 영향은 최소화되지만 문제를 신속하게 해결하고 찾는 데 도움이됩니다. -XX : + PrintGCTimeStamps -XX : + PrintGCDetails -Xloggc : gc.log

일반적으로 -Xms = -Xmx를 설정하여 고정 된 크기의 힙 메모리를 얻고 GC 수와 시간 소모를 줄이며 힙을 비교적 안정적으로 만듭니다.

-XX : + HeapDumpOnOutOfMemoryError를 사용하면 메모리 오버플로가 발생할 때 JVM이 자동으로 메모리 스냅 샷을 생성 할 수 있으므로 문제 해결에 편리합니다.

-Xmn은 젊은 세대의 크기를 설정하고, 너무 작 으면 YGC가 증가하고, 너무 크면 이전 세대의 크기가 줄어들며 일반적으로 전체 힙의 1/4에서 1/3로 설정됩니다.

-XX : + DisableExplicitGC를 설정하여 System.gc () 시스템을 금지하여 FGC의 수동 트리거로 인해 문제가 발생하지 않도록합니다.

추천

출처blog.51cto.com/7218743/2544721