이 글은 Java 메모리 할당에 대한 심층적인 이해를 위한 관련 정보를 중심으로 소개하고 있으니, 필요한 친구들이 참고하면 좋을 것 같습니다.
Java 메모리 할당에 대한 심층적인 이해
이 글은 Qian이 작성합니다. 이 글은 초보자가 자바를 더 쉽게 배울 수 있도록 자바 메모리 할당의 원리를 심도있게 소개합니다. 온라인에는 그러한 기사가 많이 있지만 대부분은 단편적입니다. 이 글은 독자들에게 인지 과정의 관점에서 체계적인 소개를 제공할 것입니다.
본 주제에 들어가기 전에 가장 먼저 알아야 할 점은 Java 프로그램이 JVM(Java Virtual Machine, Java Virtual Machine)에서 실행된다는 점입니다. JVM은 Java 프로그램과 운영 체제 간의 브리지로 이해될 수 있습니다. Java 플랫폼 독립성을 구현하는 것은 JVM의 중요성을 보여줍니다. 따라서 Java 메모리 할당의 원리를 학습할 때 모든 작업은 JVM에서 수행된다는 점을 기억해야 합니다. JVM은 메모리 할당 원리의 기초이자 전제입니다.
간단히 말해서 전체 Java 프로그램 실행 프로세스에는 다음 메모리 영역이 포함됩니다.
l 레지스터: JVM 내부 가상 레지스터, 액세스 속도가 매우 빠르고 프로그램을 제어할 수 없습니다.
l 🎜>, 즉 힙 영역 객체 의 참조(포인터)입니다. 메소드를 로드할 때 프레임을 저장하는 데에도 사용할 수 있습니다. l 힙: 은 새로운 객체 와 같이 동적으로 생성된 데이터를 저장하는 데 사용됩니다. 생성된 개체에는 해당 멤버 변수만 포함되며 멤버 메서드는 포함되지 않습니다. 동일한 클래스의 객체는 자신만의 멤버 변수를 가지며 자신의 힙에 저장되지만 클래스의 메서드를 공유하기 때문에 객체가 생성될 때마다 멤버 메서드가 복사되지 않습니다.
l 상수 풀: JVM은 로드된 각 유형에 대해 상수 풀을 유지 관리합니다. 상수 풀은 이 유형에서 사용하는 상수의 순서가 지정된 모음입니다. 직접 상수(기본 유형, 문자열) 및 기타 유형, 메소드 및 필드에 대한 기호 참조(1) 를 포함합니다. 풀의 데이터는 배열처럼 인덱스를 통해 액세스됩니다. 상수 풀에는 다른 유형, 메소드 및 필드에 대한 유형의 모든 기호 참조가 포함되어 있으므로 상수 풀은 Java의 동적 연결에서 핵심 역할을 합니다.
상수 풀은 힙에 존재합니다. l 코드 세그먼트: 는 하드 디스크에서 읽어온 소스 프로그램 코드를 저장하는 데 사용됩니다. l
데이터 세그먼트:는 static에서 정의한
static멤버를 저장하는 데 사용됩니다. 메모리 표현도는 다음과 같습니다.
사전 지식:
1.Java 파일, main 입력 방법이 있는 한, Java 프로그램은 독립적으로 컴파일되고 실행될 수 있다고 생각합니다.
2.일반형 변수든 참조형 변수(흔히 인스턴스라고 함)이든 지역 변수로 사용할 수 있고 스택에 나타날 수 있다. 단지 일반 유형 변수는 해당 값을 스택에 직접 저장하는 반면, 참조 유형 변수는 힙 영역에 대한 포인터를 저장하므로 이 포인터를 통해 힙 영역에서 이 인스턴스에 해당하는 개체를 찾을 수 있습니다. 따라서 일반 타입 변수는 스택 영역의 메모리만을 점유하는 반면, 참조 타입 변수는 스택 영역과 힙 영역의 메모리를 점유합니다.
예:
> . 2.int형 변수 date를 생성합니다. 기본형이므로 date에 해당하는 값 9가 스택에 바로 저장됩니다. 3.BirthDate 클래스의 두 인스턴스 d1과 d2를 만들고 스택의 해당 개체를 가리키는 해당 포인터를 저장합니다. 인스턴스화할 때 매개변수화된 생성자를 호출하므로 개체에 사용자 정의 초기 값이 있습니다. ~ . JVM은 이 코드를 읽을 때 i가 지역 변수임을 감지하여 i를 스택에 넣고 날짜 값을 i에 할당합니다. i에 1234를 할당합니다. 매우 간단한 단계입니다. change1 메서드가 실행된 후 로컬 변수 i가 차지하는 스택 공간은 다음과 같습니다. 즉시 석방되었습니다. 인스턴스 d1을 매개변수로 사용하여 테스트 개체의change2 메서드를 호출합니다. JVM은 Change2 메소드의 b 매개변수가 로컬 변수임을 감지하고 이를 즉시 스택에 추가합니다. 참조 유형 변수이므로 b는 d1에 포인터를 저장합니다. 이때 b와 d1은 해당 객체를 가리킵니다. 같은 힙. b와 d1 사이에 전달되는 것은 포인터입니다. > 🎜> 내부 실행 과정은 힙 영역에 새로운 객체를 생성하고, 스택의 b에 해당하는 공간에 객체의 포인터를 저장하는 것입니다. 이때 인스턴스 b는 더 이상 인스턴스 d1이 가리키는 객체를 가리키지 않습니다. 그러나 인스턴스 d1이 가리키는 객체에는 아무런 변화가 없으므로 d1에는 아무런 영향을 미칠 수 없습니다. 공간이 손실되면 힙 공간은 자동 재활용을 기다려야 합니다. 인스턴스 d2를 매개변수로 사용하여 테스트 인스턴스의change3 메서드를 호출합니다. 같은 방식으로 JVM은 스택에 로컬 참조 변수 b에 대한 공간을 할당하고 b의 d2에 포인터를 저장합니다. 이때 d2와 b는 동일한 객체를 가리킵니다. 그런 다음 인스턴스 b의 setDay 메소드를 호출하면 실제로 d2가 가리키는 객체의 setDay 메소드가 호출됩니다. 인스턴스 b의 setDay 메서드를 호출하면 d2에 영향을 줍니다. 둘 다 동일한 지점을 가리키기 때문입니다. 객체. change3 메소드가 실행된 후 로컬 참조 변수 b가 즉시 해제됩니다. 위는 Java 프로그램 실행 시 메모리 할당의 일반적인 상황입니다. 사실, 아이디어를 익히면 매우 간단합니다. 변수에는 기본 유형과 참조 유형이라는 두 가지 유형만 있습니다. 둘 다 로컬 변수로 스택에 배치됩니다. 기본 유형은 값을 스택에 직접 저장하며 참조 유형은 힙 영역에 대한 포인터만 저장합니다. 매개변수로 사용되는 경우 기본 유형은 값으로 직접 전달되고, 참조 유형은 포인터로 전달됩니다. 요약: 1.무엇이 인스턴스이고 무엇이 객체인지 명확하게 설명하세요. Class a= new Class();이때 a는 인스턴스라고 하며, 객체라고 할 수는 없습니다. 인스턴스는 스택에 있고 개체는 힙에 있습니다. 인스턴스를 작동하면 실제로는 인스턴스의 포인터를 통해 간접적으로 개체가 작동합니다. 여러 인스턴스가 동일한 개체를 가리킬 수 있습니다. 2.스택의 데이터와 힙의 데이터 소멸이 동기화되지 않습니다. 메서드가 종료되면 스택의 지역 변수는 즉시 삭제되지만 힙의 개체는 반드시 삭제되지는 않습니다. 이 개체를 가리키는 다른 변수가 있을 수 있으므로 힙의 개체를 가리키는 스택에 변수가 없을 때까지 삭제되지 않으며, 가비지 수집 스캔까지 즉시 삭제되지 않습니다. . 3.위의 스택, 힙, 코드 세그먼트, 데이터 세그먼트 등은 모두 응용 프로그램과 관련이 있습니다. 각 애플리케이션은 고유한 JVM 인스턴스에 해당합니다. 각 JVM 인스턴스는 자체 메모리 영역을 가지며 서로 영향을 미치지 않습니다. 그리고 이러한 메모리 영역은 모든 스레드에서 공유됩니다. 여기서 언급하는 스택과 힙은 전체적인 개념이며, 이러한 스택도 세분화될 수 있습니다. 4.클래스의 멤버 변수는 개체마다 다르며 고유한 저장 공간을 갖습니다(멤버 변수는 힙의 개체에 있음). 그러나 클래스의 메서드는 클래스의 모든 개체에 의해 공유됩니다. 메서드는 개체가 메서드를 사용하지 않는 경우 스택에 푸시됩니다. 위의 분석은 스택과 힙에만 관련되어 있으며 매우 중요한 메모리 영역인 상수 풀도 있는데 설명할 수 없는 문제가 자주 발생합니다. 상수 풀의 목적은 위에서 설명했으며, 로드된 클래스의 상수를 유지한다는 점만 기억하면 됩니다. 다음으로 몇 가지 예를 들어 상수 풀의 특성을 설명하겠습니다. 기본 지식: 기본 유형 및 기본 유형의 래퍼 클래스. 기본 유형은 byte, short, char, int, long, boolean입니다. 패키징 클래스의 기본 유형은 Byte, Short, Character, Integer, Long 및 Boolean입니다. 대소문자를 구분한다는 점에 유의하세요. 둘의 차이점은 기본 유형이 일반 변수로 프로그램에 반영되고, 기본 유형의 래퍼 클래스가 클래스로 프로그램에 참조 변수로 반영된다는 것입니다. 따라서 이들은 메모리의 서로 다른 위치에 저장됩니다. 기본 유형은 스택에 저장되고 기본 래퍼 클래스는 힙에 저장됩니다. 위에서 언급한 래퍼 클래스는 모두 상수 풀 기술을 구현하지만 다른 두 개의 부동 소수점 숫자 유형 래퍼 클래스는 그렇지 않습니다. 또한 String 유형은 상수 풀 기술도 구현합니다. 예: 결과: 결과분석: 1.i와 i0은 모두 공통형(int) 변수이므로 데이터가 스택에 직접 저장되는데, 스택에는 매우 중요한 기능이 있습니다. In 데이터 스택을 공유할 수 있습니다. int i = 40;을 정의한 다음 int i0 = 40;을 정의하면 스택에 40이 있는지 자동으로 확인합니다. 그렇다면 i0은 i의 40을 직접 가리키며 새 40은 추가되지 않습니다. 2.Integer는 래퍼 클래스이기 때문에 i1과 i2는 모두 참조 유형이자 스택에 포인터를 저장합니다. Integer 패키징 클래스는 상수 풀 기술을 구현하므로 i1과 i2의 40은 모두 상수 풀에서 얻어지고 동일한 주소를 가리키므로 i1=12입니다. 3.분명히 이것은 덧셈 연산입니다. Java의 수학적 연산은 모두 스택에서 수행됩니다, Java는 i1과 i2를 자동으로 계산합니다. 언박싱 연산 가 수행되어 정수 로 변환되므로 i1은 수치적으로 i2+i3과 같습니다. 4. i4와 i5는 모두 참조 유형이며 스택에 포인터를 저장합니다. Integer는 래퍼 클래스이기 때문입니다. 그러나 각각 새 항목이므로 더 이상 상수 풀에서 데이터를 찾지 않고 힙에서 새 개체를 찾은 다음 각각 개체에 대한 포인터를 저장하므로 i4와 i5는 서로 다른 포인터를 저장하므로 동일하지 않습니다. 뾰족한 물체가 다릅니다. 5.이것도 3과 같은 덧셈 연산입니다. 6.d1과 d2는 모두 참조 유형이며 Double이 래퍼 클래스이기 때문에 스택에 포인터를 저장합니다. 그러나 Double 패키징 클래스는 상수 풀 기술을 구현하지 않으므로 Doubled1=1.0;은 힙의 새 개체인 Double d1=new Double(1.0);과 동일하며 d2에도 마찬가지입니다. 따라서 d1과 d2에 저장된 포인터는 다르며 다른 객체를 가리키므로 동일하지 않습니다. 요약: 1.위에서 언급한 기본 유형의 패키징 클래스는 모두 상수 풀 기술을 구현하지만, 그들이 유지하는 상수는 [-128 ~ 127] 범위의 상수일 뿐입니다. , 개체 는 힙 에서 생성되며 더 이상 상수 풀에서 가져오지 않습니다. 예를 들어 위의 예를 Integer i1 = 400; Integer i2 = 400;으로 변경하면 127을 초과하고 상수 풀에서 상수를 가져올 수 없으므로 새로운 Integer 객체를 생성해야 합니다. 이때 i1과 i2는 동일하지 않습니다. 2. String 타입도 Constant Pool 기술을 구현하지만 약간 다릅니다. String 유형은 상수 풀에 해당 문자열 이 있는지 먼저 감지하고, 없으면 제거하고, 현재 문자열을 추가합니다. 각주: (1) 기호 참조는 이름에서 알 수 있듯이 기호입니다. 기호 참조는 이 기호가 사용될 때만 구문 분석됩니다. Linux 또는 Unix 시스템에 익숙하다면 이 기호 참조를 파일에 대한 소프트 링크로 생각할 수 있습니다. 이 소프트 링크를 사용하면 실제 파일을 찾기 위해 실제로 구문 분석되고 확장됩니다. 심볼 참조의 경우 클래스 로딩 수준에서 더 많은 논의가 있으며, 소스 코드 수준은 단지 형식적인 논의일 뿐입니다. 클래스가 로드되면 해당 클래스에서 사용하는 다른 클래스의 기호 참조가 상수 풀에 저장됩니다. 실제 코드가 실행될 때 JVM은 다른 클래스를 만나면 상수 풀을 저장합니다. 처음에는 클래스의 기호 참조가 확장되어 직접 참조로 변환되므로 다음에 동일한 유형이 발견될 때 JVM은 더 이상 이를 구문 분석하지 않고 구문 분석된 이 직접 참조를 직접 사용합니다. 클래스 로딩 과정에서 위의 기호 참조문 외에도 소스 코드 레벨에서는 참조 파싱 과정을 기반으로 코드 내 특정 데이터가 기호 참조인지 직접 참조인지 구분합니다. , 예: System.out.println("test" + "abc");//여기서 발생하는 효과는 직접 참조와 동일하며 특정 Strings = "abc"를 가정합니다. ; System.out.println("test" + s);//여기서의 효과는 기호 참조와 동일합니다. 즉, s는 확장되고 구문 분석됩니다. "abc"의 심볼릭 링크, 즉 컴파일된 파일에서 이때 클래스 파일은 s를 직접 확장하지 않고 이 s를 심볼로 처리하여 실제 코드가 실행될 때 확장됩니다. public class test {
public static void main(String[] args) {
objPoolTest();
}
public static void objPoolTest() {
int i = 40;
int i0 = 40;
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
Double d1=1.0;
Double d2=1.0;
System.out.println("i=i0\t" + (i == i0));
System.out.println("i1=i2\t" + (i1 == i2));
System.out.println("i1=i2+i3\t" + (i1 == i2 + i3));
System.out.println("i4=i5\t" + (i4 == i5));
System.out.println("i4=i5+i6\t" + (i4 == i5 + i6));
System.out.println("d1=d2\t" + (d1==d2));
System.out.println();
}
}
i=i0 true
i1=i2 true
i1=i2+i3 true
i4=i5 false
i4=i5+i6 true
d1=d2 false
위 내용은 Java의 메모리 할당에 대한 심층적인 이해(그림 및 텍스트)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!