이 글에서는 주로 Java런타임 데이터 영역, 객체 생성, 가비지 수집 알고리즘을 포함한 Java 메모리 할당 및 재활용 메커니즘에 대해 설명합니다. 재활용 전략.
PHP 중국어 홈페이지 강좌 "JAVA 초등 입문 동영상 튜토리얼"을 참고하세요. 저자는 튜토리얼 내용을 바탕으로 문화를 요약하고 그림만 그렸습니다. 이 부분의 내용은 이해와 기억을 돕기 위해 최대한 그림이나 글, 표 등의 형태로 제시하였습니다.
다음 그림은 Java Virtual Machine이 실행 중일 때의 메모리 다이어그램입니다.
그림에서 Java 메모리가 6개 부분으로 나누어져 있음을 알 수 있습니다.
프로그램 카운터: 각 스레드에는 독립적인 프로그램 카운터가 있으며, 카운터는 현재 스레드에서 실행되는 바이트코드의 줄 번호 표시로 간주될 수 있습니다. 바이트코드 인터프리터가 작동하면 이 카운터의 값을 변경하여 실행해야 하는 다음 바이트코드 명령어, 분기, 루프 , 점프, 예외 처리 등을 선택합니다. 스레드 복구는 이 카운터에 의존합니다.
Java 가상 머신 스택: 가상 머신 스택은 스레드 전용이며 수명 주기는 스레드와 동일합니다. 가상 머신 스택은 Java 메소드 실행을 위한 메모리 모델 을 기술하며, 각 메소드가 실행되면 로컬 변수 테이블, 피연산자 스택, 동적 링크를 저장하는 스택 프레임이 생성됩니다. 방법. 수출 및 기타 정보. 각 메소드의 호출부터 실행 완료까지의 과정은 스택 프레임을 가상머신 스택에 밀어넣고 튀어나오는 과정에 해당합니다.
로컬 메서드 스택: 가상 머신 스택과 비슷한 역할을 합니다. 차이점은 가상 머신 스택이 Java 메소드 실행을 제공하고 로컬 메소드 스택이 네이티브 메소드 를 제공한다는 것입니다.
모든 스레드가 공유하는 영역입니다. 가상 머신이 시작될 때 생성되며 거의 모든 객체 인스턴스가 힙에 할당됩니다. Java 힙은 New Generation과 Old Generation으로 세분화할 수도 있습니다. 더 자세히는 Eden 공간, From Survivor 공간, To Survivor 공간이 있습니다. 그러나 어떻게 분할하더라도 모든 객체 인스턴스는 저장됩니다. 추가 분할의 목적은 메모리를 더 잘 회수하거나 메모리를 더 빠르게 할당하는 것입니다.
메서드 영역은 각 스레드가 공유하는 메모리 영역으로 주로 가상머신이 로딩한 클래스 정보를 저장하는데 사용된다. 상수, 정적 변수, 즉시 컴파일된 코드 및 기타 데이터. 이 영역은 Java 힙과 같은 연속 메모리가 필요하지 않으며 고정 또는 확장이 가능하며 가비지 수집을 구현하지 않도록 선택할 수도 있습니다. 이 영역의 메모리 재활용 대상은 주로 상수 풀의 재활용과 유형 언로드를 위한 것입니다. 이 영역에서는 가비지 수집 동작이 거의 발생하지 않습니다.
인터페이스 등의 설명 정보 외에도 클래스 파일에는 생성된 다양한 데이터를 저장하는 데 사용되는 상수 풀이 포함되어 있습니다. 컴파일하는 동안 리터럴 문자 및 기호 는 을 참조합니다. 이 부분은 클래스가 로드된 후 메서드 영역의 런타임 상수 풀에 저장됩니다.
직접 메모리는 오프힙 메모리라고도 합니다. NIO 클래스는 JDK1.4 이후에 도입된 클래스로, Channel과 Buffer 기반의 I/O 방식으로 NativeFunction 라이브러리를 이용하여 힙 외부에 메모리를 직접 할당한 후 The를 통해 저장할 수 있다. Java 힙의 DirectByteBuffer 개체는 이 메모리에서 참조로 작동합니다. 이렇게 하면 성능이 크게 향상되고 Java 힙과 기본 힙 간에 데이터가 앞뒤로 복사되는 것을 방지할 수 있습니다.
数据区域 | 概括 | 线程共享 |
---|---|---|
程序计数器 | 当前线程所执行的字节码的行号指示器 | 否 |
虚拟机栈 | 为Java方法执行创建栈帧存储局部变量、操作数栈、动态链接、方法出口等信息 | 否 |
本地方法栈 | 与虚拟机栈类似,为Native方法服务 | 否 |
堆 | 存放对象实例 | 是 |
方法区 | 存储虚拟机已加载的类信息、常量、静态变量、即时编译后的代码等数据 | 是 |
运行时常量池 | 方法区的一部分,存放编译期生成的字面量和符号引用 | 是 |
直接内存 | 被分配在堆外的内存,性能高,不受Java堆的大小限制 | 是 |
위 그림은 객체 생성의 전체 흐름도이며, 이에 대해서는 다음에 자세히 설명하겠습니다.
가상 머신이 new 명령어를 받으면 이 명령어의 매개변수가 상수 풀에서 클래스의 기호 참조를 찾을 수 있는지 확인하고 이 기호 참조가 나타내는 클래스가 로드, 확인 및 초기화되었는지 확인합니다. 그렇지 않은 경우 클래스 로딩 과정을 먼저 수행해야 합니다.
클래스 로딩이 완료된 후 객체 할당에 필요한 공간을 결정할 수 있습니다. Java 힙의 메모리가 절대적으로 규칙적이고 사용된 메모리가 한쪽에 저장되고 사용 가능한 메모리가 다른 쪽에 저장되고 중앙에 경계 지점 표시로 포인터가 있는 경우 메모리 할당은 이동의 문제일 뿐입니다. 포인터가 일정량만큼 여유 공간을 향하게 됩니다. 이 할당 방법을 "포인터 충돌"이라고 합니다. Java 힙의 메모리가 규칙적이지 않고 여유 메모리와 사용 메모리가 인터리브되어 있는 경우 가상 머신은 어떤 메모리 블록이 사용 가능한지 기록하기 위한 목록을 유지하고, Object 인스턴스 할당 시 해당 목록에서 할당할 충분한 공간을 찾아내야 합니다. 목록의 기록을 업데이트합니다. 이 할당 방법을 "무료 목록"이라고 합니다. 어떤 할당 방법을 사용할지는 일반적으로 가상 머신의 가비지 수집기에 압축 기능이 있는지 여부에 따라 결정됩니다.
사용 가능한 공간을 나눌 때, 객체 인스턴스에 공간을 할당할 때 안전한지 여부도 고려해야 합니다. 스레드 안전성을 보장하기 위한 두 가지 옵션이 있습니다. 하나는 메모리 공간 할당 작업을 동기화하는 것입니다. 실제로 가상 머신은 업데이트 작업의 원자성을 보장하기 위해 실패 재시도 기능이 있는 CAS를 사용합니다. 다른 하나는 메모리 할당 작업을 스레드에 따라 서로 다른 공간으로 나누는 것입니다. 각 스레드는 Local Thread Allocation Buffer(Thread Local Allocation Buffer, TLAB)라는 Java 힙에 작은 메모리 조각을 미리 할당합니다. . 메모리를 할당하려는 스레드는 해당 스레드의 TLAB에 메모리를 할당합니다. 동기화 잠금은 TLAB가 모두 사용되고 새 TLAB가 할당되는 경우에만 필요합니다.
메모리 할당이 완료된 후 가상 머신은 에서 할당한 메모리 공간을 0 값 (객체 헤더 제외)으로 초기화하여 인스턴스 필드가 객체의 Java에 초기 값을 할당하지 않고 코드에서 직접 사용할 수 있습니다.
가상 머신은 개체의 정보를 개체의 개체 헤더 에 넣습니다.
객체의 메모리 레이아웃은 세 부분으로 나뉩니다.
객체 헤더 여기에는 주로 두 부분의 정보가 포함됩니다.
한 부분은 해시 코드, GC와 같은 개체 자체의 런타임 데이터를 저장하는 데 사용됩니다. 세대 연령, 잠금 상태 플래그, 스레드가 보유한 잠금, 편향된 스레드 ID, 편향된 타임스탬프 등
다른 부분은 클래스 메타데이터에 대한 개체의 포인터인 유형 포인터입니다. 가상 머신은 이 포인터를 사용하여 개체가 어떤 클래스인지 결정합니다. 의 사례입니다. 객체가 Java배열인 경우 객체 헤더에 배열의 길이를 기록하는 데이터 조각이 있어야 합니다.
인스턴스 데이터 부분은 객체가 실제로 저장하고 있는 유효한 정보이며, 프로그램에서 정의한 각종 필드의 내용이기도 하다. 암호. 상위 클래스 에서 상속되고 하위 클래스에 정의된 모든 내용을 기록해야 합니다.
여백 정렬 은 자리 표시자 역할만 합니다. HotSpot VM의 자동 메모리 관리 시스템에서는 개체 시작 주소가 8바이트의 정수 배수여야 하므로 개체 크기는 8바이트의 정수 배수여야 합니다. 객체 인스턴스 데이터 부분이 정렬되지 않은 경우 정렬 패딩으로 완성해야 합니다.
Java Virtual Machine은 도달성 분석을 통해 객체가 살아 있는지 여부를 판단합니다. 이 알고리즘의 기본 아이디어는 "GC Roots"라는 일련의 개체를 시작점으로 사용하고 이러한 노드 에서 아래쪽으로 검색하는 것입니다. 검색이 이동하는 경로를 참조 체인이라고 합니다. 개체가 GC 루트에 도달하면 참조 체인에 연결되어 있지 않으면 개체를 사용할 수 없습니다.
그림과 같이 object5, object6, object7은 서로 연관되어 있지만 GC Roots는 도달 불가능하므로 재활용 가능한 객체로 판단됩니다.
또 언급할 가치가 있는 것은 참조 계산 알고리즘입니다. 참조 계산 방법은 참조가 있을 때마다 개체에 참조 카운터를 제공하는 것입니다. 카운터 값이 1로 감소합니다. 언제든지 카운터가 0인 개체는 더 이상 사용할 수 없습니다. 참조 카운터는 효율적이고 구현이 간단합니다. 그러나 객체 간 순환 참조 문제를 해결하기는 어렵습니다. 거의 모든 주류 Java 가상 머신은 더 이상 참조 카운팅을 사용하여 메모리를 관리하지 않습니다.
도달성 분석 알고리즘에서 객체에 도달할 수 없는 경우에도 즉시 도달하지 않을 수 있습니다. 재활용. 물건을 재활용할 때에는 최소 2번 이상 마킹 과정을 거쳐야 합니다.
접근성 분석 후 객체에 GC Roots에 연결된 참조 체인이 없으면 처음으로 표시되고 한 번 필터링됩니다. 필터링 조건은 이 객체가 finalize() 메서드를 실행하는 데 필요한지 여부입니다. 객체가 finalize() 메서드를 포함하지 않거나 가상 머신이 finalize() 메서드를 호출한 경우 가상 머신은 이 두 가지 상황을 "실행할 필요 없음"으로 처리합니다.
이 개체가 finalize() 메서드를 실행하는 데 필요하다고 판단되면 이 개체는 F-Queue대기열에 배치되며 나중에 가상 머신에서 낮은 수준으로 자동 생성됩니다. 우선순위Finalizer 스레드는 finalize() 메서드를 실행합니다. GC는 F-Queue의 개체에 대해 두 번째 소규모 표시를 수행합니다. 개체가 참조 체인의 개체와 다시 연결되면 두 번째 표시 중에 "곧 재활용됨" 컬렉션에서 제거됩니다. . 그렇지 않으면 개체가 실제로 재활용됩니다.
방법 영역의 재활용은 크게 두 부분으로 구성됩니다. : 사용되지 않는 상수 및 쓸모없는 클래스.
버려진 상수를 재활용하는 것은 Java 힙의 객체를 재활용하는 것과 유사합니다.
무용한 클래스를 판단하는 조건은 세 가지 조건을 충족해야 합니다.
이 클래스의 모든 인스턴스가 재활용되었습니다.
이 클래스를 로드한 ClassLoader가 재활용되었습니다.
이 클래스에 해당하는 java.lang.Class 객체는 어디에서도 참조되지 않으며, 리플렉션을 통해 해당 클래스에 접근할 수 없습니다.
Mark-Sweep 알고리즘(Mark-Sweep) :
알고리즘은 "마킹"과 "삭제"의 두 단계로 나누어집니다. 먼저 재활용이 필요한 개체를 표시하고 마킹이 완료된 후 표시된 개체를 균일하게 재활용합니다. 두 가지 주요 단점이 있습니다. 첫째, 효율성 문제, 마킹 및 클리어 프로세스 모두 그다지 효율적이지 않습니다. 두 번째는 공간 문제입니다. 마크가 지워진 후 불연속적인 메모리 조각이 너무 많이 생성되면 더 큰 개체를 할당할 때 메모리 공간이 부족해질 수 있습니다. 가비지 수집 작업이 미리 시작되어야 합니다.
복사 알고리즘 :
복사 알고리즘 사용 가능한 메모리를 용량에 따라 동일한 크기의 블록 두 개로 나누고 한 번에 한 블록만 사용하십시오. 메모리 블록이 부족해지면 남아 있는 객체를 다른 블록에 복사한 후 사용된 메모리 공간을 한번에 정리하세요. 이렇게 하면 매번 절반 영역 전체를 재활용할 수 있으며 메모리 할당 시 메모리 조각화를 고려할 필요가 없습니다. 힙의 최상위 포인터를 이동하여 순서대로 메모리를 할당하면 됩니다. , 효율적으로 실행. 단지 이 알고리즘이 메모리를 원래 크기 의 절반으로 줄인 것인데, 이는 더 비쌉니다.
Mark-Collation 알고리즘(Mark-Compact) :
마킹 과정은 "mark-clear" 알고리즘과 동일하지만 재활용 가능한 개체를 직접 정리하는 대신 살아남은 모든 개체를 한쪽 끝으로 이동한 다음 끝 경계 외부의 메모리를 직접 삭제합니다
4.세대 수집 알고리즘
상용 가상 머신의 가비지 수집은 개체 생존 주기에 따라 메모리를 여러 블록으로 나누는 세대별 수집 알고리즘을 채택합니다. Java 힙은 New Generation과 Old Generation으로 구분되어 시대적 특성에 따라 적절한 수집 알고리즘을 사용할 수 있다. 새로운 세대의 각 가비지 수집 중에 많은 수의 개체가 삭제되므로 복제 알고리즘을 사용하십시오. Old Generation의 개체 생존율은 높으며 할당 보장을 위한 추가 공간이 없습니다. "mark-clean" 또는 "mark-organize" 알고리즘을 사용하여 재활용하는 데 적합합니다.
Eden 파티션에서 객체 우선순위:
대부분의 경우 신세대 Eden 영역에 객체가 할당됩니다. Eden 영역에 할당할 공간이 충분하지 않으면 가상 머신은 Minor GC를 시작합니다. GC 이후 해당 객체는 Survivor 공간에 들어가려고 하는데, Survivor 공간에서 해당 객체를 넣을 수 없는 경우에는 공간 할당 보장 메커니즘을 통해서만 사전에 Old Generation으로 전달될 수 있습니다.
대형 객체는 Old Generation에 직접 진입합니다.
대형 객체는 많은 양의 연속 메모리 공간이 필요한 Java 객체를 말합니다. 가상 머신은 -XX:PretenureSizeThreshold 매개변수를 제공합니다. 개체가 이 설정 값보다 크면 해당 개체는 이전 세대에 직접 할당됩니다. 이렇게 하면 새로운 세대의 Eden 영역과 두 Survivor 영역에서 대량의 메모리 복사를 피할 수 있습니다.
장기 생존 객체가 Old Generation으로 진입:
가상 머신은 각 객체에 대한 객체 수명 카운터를 정의합니다. 객체가 Eden에서 태어나 Minor GC 후에도 여전히 생존하고 생존자가 수용할 수 있는 경우 생존자 공간으로 이동되고 객체 연령은 1로 설정됩니다. 각 Minor GC 후에도 객체는 여전히 생존합니다. Survivor 영역에, age를 하나만 추가하면, age가 -XX:MaxTenuringThreshold 파라미터에 설정된 값에 도달하면 Old Generation으로 이동하게 됩니다.
동적 연령 판단:
가상 머신은 개체를 다음으로 이동하기 전에 개체의 연령이 -XX:MaxTenuringThreshold에 의해 설정된 값에 도달해야 한다고 항상 요구하지는 않습니다. 구세대. Survivor에서 같은 age의 모든 객체의 크기의 합이 Survivor 공간의 절반보다 크면 age가 이 age보다 크거나 같은 객체는 Old Generation에 직접 들어갈 수 있습니다.
공간 할당 보장:
Minor GC 이전에 가상 머신은 이전 세대에서 사용 가능한 최대 연속 공간이 새 세대의 모든 객체의 총 공간보다 큰지 확인합니다. 조건이 true이면 Minor GC가 설정됩니다. 그렇지 않은 경우 가상 머신은 HandlePromotionFailure 설정 값이 보증 실패를 허용하는지 확인합니다. 허용되는 경우 이전 세대에서 사용 가능한 최대 연속 공간이 이전 세대로 이동된 개체의 평균 크기보다 큰지 계속 확인합니다. 더 큰 경우 Minor GC가 시도됩니다. 보다 작거나 HandlePromotionFailure 설정 값이 위험을 허용하지 않는 경우 Full GC가 수행됩니다.
신세대 GC(Minor GC): 신세대에서 발생하는 가비지 컬렉션 동작은 대부분의 Java 객체가 빨리 죽기 때문에 Minor GC가 매우 빈번하고 재활용 속도도 빠르다. 그것도 천천히.
Old Generation GC(Major GC/Full GC): Old Generation에서 발생하는 가비지 수집 작업입니다. Major GC에는 종종 하나 이상의 Minor GC가 수반됩니다. Major GC의 속도는 일반적으로 Minor GC보다 10배 이상 느립니다.
위 내용은 Java 메모리 할당 및 재활용 메커니즘에 대한 자세한 설명(그림)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!