JVM의 개체 및 참조

JVM의 개체 및 참조

JVM에서 개체 생성 프로세스

개체 메모리 할당

 

가상 머신이 새 명령어를 만나면 먼저 클래스에서로드되었는지 여부를 확인합니다. 그렇지 않은 경우 해당 클래스 로딩을 먼저 수행해야합니다. 클래스로드는 JVM 런타임 데이터 영역으로 클래스 파일을로드하는 프로세스입니다.

 

1.  로딩 확인

먼저이 명령어의 매개 변수가 상수 풀에서 클래스의 기호 참조를 찾을 수 있는지 확인하고 ( 기호 참조 : 기호 참조는 기호 집합을 사용하여 참조 된 대상을 설명 함 ) 클래스가로드, 해결 및 초기화되었는지 확인합니다. .

 

2.  메모리 할당

다음으로 가상 머신은 새로 태어난 개체에 대한 메모리를 할당합니다. 객체에 공간을 할당하는 작업은 Java 힙에서 특정 크기의 메모리를 나누는 것과 같습니다.

   포인터 충돌

Java 힙의 메모리가 절대적으로 규칙적인 경우 사용 된 모든 메모리는 한쪽에 배치되고 사용 가능한 메모리는 다른쪽에 배치됩니다. 중간에 구분선으로 포인터가 있습니다. 메모리가 계속 할당되면 포인터는 개체와 동일한 크기의 여유 메모리 및 메모리 영역으로 만 이동하면됩니다 (개체 크기가 정렬되고 채워지고 채워진 개체 메모리는 8 바이트의 배수입니다. , 따라서 포인터 충돌에 의해 저장된 객체는 여전히 일반 공간) 메모리의 연속성을 보장합니다.이 할당 방법을 "포인터 충돌"이라고합니다 .

image.png

무료 목록

자바 힙의 메모리가 규칙적이지 않고 사용 된 메모리와 여유 메모리가 서로 인터리브되면 단순히 포인터를 충돌시킬 방법이 없습니다. 현재 가상 머신은 사용 가능한 영역을 기록하기 위해 테이블을 유지해야합니다. 메모리를 할당 할 때 목록에서 개체 인스턴스에 할당 할 충분한 공간을 찾고 목록의 레코드를 업데이트합니다.이 할당 방법을 "free list"라고합니다 .

image.png

어떤 방법이 사용되는지는 말뚝의 존재 여부에 따라 다릅니다. 힙의 존재 여부는 가비지 수집기에 압축 기능이 있는지 여부에 따라 결정됩니다.

여기에 한 가지 더 언급 : 가비지 컬렉션에서는 CMS 가비지 컬렉션 만 사용 가능한 목록을 생성하고 나머지는 포인터 충돌입니다.

 

동시성 보안 문제

객체의 저장 공간을 나누는 방법 외에도 가상 머신에서 객체를 생성하는 빈도가 매우 높다는 점을 고려해야합니다. 힙 메모리는 스레드 공유 영역입니다. 여러 스레드가 동시에 동일한 영역에 액세스 할 때 두 스레드는 영역이 동시에 사용 가능한 영역이라고 판단하지만 첫 번째 개체가 메모리를 할당 한 후 두 번째 개체는 여전히 스레드 안전 문제를 생성하는 할당 된 메모리.

해결책

이런 종류의 문제에 대한 두 가지 해결책이 있습니다

CAS 메커니즘

image.png

CAS의 실제 목적은 실제로 3 개 또는 71 개에 대해 신경 쓰지 않고 먼저 작업을 수행 한 다음 유용할지 여부를 결정합니다. CPU 성능을 희생하면서 실행 효율성을 높이기 위해 노력하십시오.

TLAB 메커니즘 (할당 버퍼)

다른 하나는 스레드 단위로 힙 메모리의 메모리 할당 작업을 나누는 것입니다. 즉, 각 스레드가 Java 힙에 작은 개인 메모리를 미리 할당합니다. 즉, 로컬 스레드 할당이 (Thread Local Allocation Buffer, TLAB), JVM 스레드가 초기화되면 현재 메모리에서만 사용되는 지정된 크기의 메모리에도 적용됩니다. 이러한 방식으로 각 쓰레드는 별도의 Buffer를 가지며, 메모리 할당이 필요한 경우 자체 Buffer에 할당되므로 경쟁이 없으며 할당 효율을 크게 향상시킬 수 있습니다. 버퍼 용량이 충분하지 않은 경우 Edsen 지역에서 다른 부품을 신청하여 계속 사용하십시오.

TLAB의 목적은 각 Java 응용 프로그램 스레드가 새 개체에 메모리 공간을 할당 할 때 자체 전용 메모리 포인터를 사용하여 메모리를 할당하도록 허용하여 동기화 오버 헤드를 줄이는 것입니다.

TLAB는 각 스레드가 포인터를 개인적으로 할당하도록 허용합니다. 그러나 지하에 남아있는 개체의 메모리 공간은 여전히 ​​모든 스레드에 액세스 할 수 있지만 다른 스레드는이 영역에 할당 할 수 없습니다. 한 스레드의 TLAB가 가득 찬 경우 해당 스레드의 다른 TLAB를 신청하십시오.

스레드 안전성 문제를 근본적으로 해결하는 것과 같습니다.

image.png

3.  메모리 공간 초기화

메모리 할당이 완료된 후 가상 머신은 할당 된 메모리 공간을 0 값으로 초기화해야합니다 (멤버 변수 초기화. 이때 멤버 변수는 int = 0; boolean = false와 같이 힙 메모리에 있습니다.) ). 이 작업 단계는 객체의 인스턴스 필드를 초기 값을 할당하지 않고 Java 코드에서 직접 사용할 수 있음을 확인합니다. 프로그램은 이러한 필드의 데이터 유형에 해당하는 0 값에 액세스 할 수 있습니다.

4.  설정

다음으로 가상 머신은 개체에 필요한 설정을 지정해야합니다. 예를 들어이 객체가 속한 클래스 인스턴스, 클래스 메타 데이터 정보 (Java 클래스는 Java 핫스팟 VM 내에서 클래스 메타 데이터로 표시됨), 객체의 해시 코드 및 객체의 GC 생성 연령을 어떻게 찾을 수 있습니까? 이 정보는 개체의 개체 헤더에 저장됩니다.

 

5.  개체 초기화

위의 작업이 완료된 후 가상 머신의 관점에서 새 객체가 생성되었지만 Java 관점에서는 객체 생성이 시작되었습니다. 모든 필드는 0 값입니다. 따라서 new 실행 후 프로그래머의 희망 (구성 방법)에 따라 객체가 초기화됩니다. 정말 쓸모있는 물건이 만들어집니다.

 

개체 메모리 레이아웃

image.png

 

HotSpot에서 메모리에 저장된 개체의 레이아웃은 개체 헤더 (Header), 인스턴스 데이터 (Instance Data) 및 정렬 패딩 (Padding)의 세 영역으로 나눌 수 있습니다.

객체 헤더에는 정보의 두 부분이 포함됩니다. 첫 번째 부분은 HashCode, GC 생성 기간, 잠금 상태 플래그, 스레드가 보유한 잠금, 바이어스 스레드 ID, 바이어스 타임 스탬프와 같은 자체 객체 런타임 데이터를 저장하는 데 사용됩니다.

객체 헤더의 다른 부분은 유형 포인터, 즉 객체가 클래스 메타 데이터 (메서드 영역의 클래스 데이터)를 가리키는 포인터입니다. 가상 머신은이 포인터를 사용하여 객체가 어떤 클래스 인스턴스인지 결정합니다.

객체가 Java 배열 인 경우 객체 헤더에 배열 길이를 기록하기위한 데이터도 있습니다.

정렬 및 패딩의 세 번째 부분은 반드시 존재하는 것은 아니며 특별한 의미도 없으며 자리 표시 자 역할 만합니다. HotSpot VM의 자동 메모리 관리 시스템에서는 개체의 크기가 8 바이트의 정수 배수 여야하기 때문입니다. 개체의 다른 데이터 부분이 정렬되지 않은 경우 정렬로 채워야합니다.

 

개체 액세스 위치

객체를 사용하기 위해 객체가 생성됩니다. Java 프로그램은 스택의 참조 데이터를 통해 힙의 특정 객체를 조작해야합니다. 현재 주류 액세스 방법에는 핸들 및 직접 포인터 사용이 포함됩니다.

 

핸들

image.png

이때 저장된 참조는 핸들 풀의 주소입니다. 참조로 객체에 액세스 할 때 먼저 핸들 풀에 액세스해야합니다. 핸들 풀에는 개체 인스턴스 데이터에 대한 포인터와 개체 유형 데이터에 대한 포인터가 있습니다. 객체에 액세스하는 단계가 하나 더있는 것과 같으므로 핸들 풀을 사용하면 힙에 새 영역이 생성됩니다.

핸들 풀의 장점 : 객체의 위치가 변경되면 (예 : 가비지 수집 중에 태그 정렬 알고리즘이 객체의 주소를 변경 함) 스택에서 참조 주소를 수정할 필요가 없습니다. 주소의 핸들 풀에서 포인터를 변경해야합니다. 안정성을 가져오고 성능을 잃습니다.

 

직접 포인터

image.png

직접 포인터 액세스를 사용하는 경우 개체 주소가 참조에 직접 저장됩니다.

이 두 가지 개체 액세스 방법에는 고유 한 장점이 있습니다. 직접 포인터 액세스 사용의 가장 큰 장점은 빠르며 포인터 포지셔닝의 오버 헤드 시간을 절약 할 수 있다는 것입니다. 객체 액세스는 Java에서 매우 빈번하기 때문에 이러한 유형의 오버 헤드도 누적 될 때 매우 객관적인 실행 비용입니다.

HotSpot은 개체 액세스를 위해 직접 포인터 액세스를 사용합니다.

 

피사체의 생존 판단

힙에는 거의 모든 객체 인스턴스가 저장됩니다 (객체의 일부는 가상 머신 최적화 기술에 의해 스택 메모리에 할당 됨). 가비지 컬렉터가 개체를 수집하기 전에해야 할 일은 이러한 가비지 중 어떤 것이 "살아있는"것인지, "죽은"것인지 확인하는 것입니다. 그러면 어떤 객체가 죽은 것으로 간주됩니까?

 

쓰레기 생성 과정

 image.png

그림에서 보면 처음에는 Study 개체가 생성되었지만 참조에서 연결이 끊어진 것을 알 수 있습니다. 즉, 참조에 의해 참조되지 않는 하나 이상의 객체가 가비지가됩니다.

 

쓰레기 판단 방법

참조 계산

개체에 참조 카운터를 추가합니다. 사용할 장소가있을 때마다 카운터가 1 씩 증가하고 참조가 무효화되면 step1이 감소합니다.

image.png

2 단계:

image.png

 

step3 :

image.png

이때 오브젝트 2와 오브젝트 4는 이론적으로 쓰레기이지만 참조 횟수가 0이 아니므로 재활용 할 수 없습니다.

 

이 방법은 여전히 ​​파이썬에서 정크를 판단하는 데 사용되지만 주류 가상 머신은 사용되지 않습니다. 객체가 서로를 참조하는 상황이 있기 때문입니다. 이때 효율성에 영향을 미치는 이러한 문제를 처리하기 위해 추가 메커니즘을 도입해야합니다.

 image.png

image.png

각 개체에 10M 바이트 배열을 만듭니다. 두 개체는 총 20M에 대해 서로를 참조합니다. 가비지가 수집 될 때 메모리 공간은 25M에서 0.8M으로 재 확보되며 이는 HotSpot에서 순환 참조 된 두 개체가 재 확보되었음을 입증합니다.

 

접근성 분석 알고리즘 (면접 초점)

객체가 살아 있는지 판단하기 위해이 알고리즘의 아이디어는 "GC Roots"라는 일련의 객체를 시작점으로 사용하여 이러한 노드에서 시작하여 아래쪽으로 검색하는 것입니다. 검색 중에 전달 된 경로를 Reference라고합니다. 체인 : 객체가 참조 체인에 의해 GC Roots에 연결되지 않은 경우 해당 객체를 사용할 수 없음을 증명합니다. GC Roots의 대상은 다음과 같습니다 (처음 4 가지 유형에 초점)

1. 가상 머신 스택에서 참조 (스택 프레임의 로컬 변수 테이블). 각 스레드의 가상 머신 스택에있는 로컬 변수.

2. 메소드 영역의 클래스 정적 속성에 대한 참조 : Java의 참조 유형 정적 변수.

3. 메서드 영역의 상수 참조 : 문자열 상수 풀의 리터럴 값에 해당하는 참조.

4. 네이티브 메서드 스택의 JNI (일반적으로 호출하는 네이티브 메서드)의 변수.

5. JVM 내부 참조 (클래스 개체, 예외 개체, 클래스 로더 등)

6. 동기화 잠금이 보유한 개체입니다.

7. JVM의 JMXBean, JVMTI에 등록 된 콜백, 로컬 코드 캐시 등

8. JVM 구현의 "임시 변수 객체", 여러 세대에 걸쳐 참조되는 객체 (생성 모델을 사용하여 객체 생성의 일부만 재활용하는 경우 나중에 이에 대해 설명하고 일반적인 이해를 얻습니다)

 

수업 회복 조건

등급의 복구 조건은 상대적으로 가혹하며 다음 조건을 동시에 충족해야합니다 (가능할 뿐이며 불가피한 것은 아니므로 제어 할 매개 변수가 여전히 있음). 매개 변수를 제어하여 가비지 콜렉션을 금지하여 효율성을 높일 수 있습니다.

1.이 클래스의 모든 인스턴스가 재활용되었습니다.

2. 클래스를로드 한 ClassLoader가 재활용되었습니다.

3.이 클래스에 해당하는 Java.lang.Class 객체는 어디에도 참조되지 않으며이 클래스의 메서드는 리플렉션을 통해 어디에서나 액세스 할 수 없습니다.

 

방법 마무리

접근성 분석에 의해 판단 된 대상이 의무적이지 않더라도 현재로서는 "검증 단계"에 있습니다. 개체가 실제로 죽었다고 선언하려면 마킹 프로세스를 두 번 거쳐야합니다. 한 번 GC 루트가있는 참조 체인을 찾을 수 없습니다. 이번에 처음으로 표시됩니다. 스크리닝 후 (객체가 파이널 라이즈를 다루는 경우) 파이널 라이즈에 저장할 수 있습니다.

코드 데모 :

image.png

image.png

피험자가 처음에는 성공적으로 구출되었지만 두 번째에는 구출되지 않았 음을 알 수 있습니다. 참고 : 개체를 저장할 때 개체에 대한 참조를 다시 추가해야합니다. 그렇지 않으면 여전히 유지되지 않습니다.

image.png

image.png

GC가 실행 된 후 Finalize 메서드가 실행되면 개체가 복구되지 않는 것을 볼 수 있습니다. 따라서 Finalize의 정확성을 보장 할 수 없으므로 권장하지 않습니다.

 

다른 언어로 된 가비지 수집

C 언어 응용 프로그램 메모리 malloc 무료

C ++ : 새 삭제

C / C ++ 수동으로 메모리 회수

자바 : 새로운

Java는 자동 메모리 복구이고 프로그래밍이 간단하며 시스템에 오류가 발생하지 않습니다.

메모리를 수동으로 해제하면 두 가지 유형의 문제가 발생하기 쉽습니다.

1 : 재활용하는 것을 잊었습니다.

C / C ++ 수동 재활용, 재활용을 잊어 버리면 메모리 누수 발생

2 : 다중 재활용

두 개체가

malloc 무료 무료

B malloc     

이때 내 스레드가 생성 한 객체가 다른 스레드에 의해 재활용 될 수 있습니다.

 

 

 

추천

출처blog.csdn.net/weixin_47184173/article/details/109562690