Java 메모리 할당 및 관리는 Java의 핵심 기술 중 하나입니다. 우리는 이전에 Java의 메모리 관리, 메모리 누출 및 Java Garbage Collection에 대한 지식을 소개했습니다. 오늘, 우리는 다시 한 번 Java Core에 깊이 들어가서 메모리 할당에서 Java에 대한 지식을 자세히 소개합니다. 일반적으로 Java는 메모리를 할당 할 때 다음과 같은 영역을 포함합니다.
◆ 레지스터 : 프로그램에서는 제어 할 수 없습니다
◆ 스택 : 기본 유형의 데이터 및 객체에 대한 참조를 저장하지만 객체 자체는 스택에 저장되지는 않지만 힙에 저장됩니다 (새로 나오는 객체)
◆ 힙 : 새로 생성 된 저장 데이터
◆ 정적 도메인 : 정적으로 정의 된 물체에 저장된 정적 멤버
◆ 상수 수영장 : 상수를 보관하십시오
◆ 비 램 스토리지 : 하드 디스크와 같은 영구 저장 공간
Java 메모리 할당에 쌓입니다
객체의 함수 및 기준 변수에 정의 된 일부 기본 유형의 변수 데이터는 모두 함수의 스택 메모리에 할당됩니다.
변수가 코드 블록에 정의되면 Java는 스택의 변수에 대한 메모리 공간을 할당합니다. 변수가 범위를 종료하면 Java는 변수에 할당 된 메모리 공간을 자동으로 해제하고 메모리 공간을 즉시 별도로 사용할 수 있습니다. 스택의 데이터 크기와 수명주기는 확실하며,이 데이터는 데이터를 참조 할 수 없을 때 사라집니다.
Java 메모리 할당 힙
힙 메모리는 새로 생성 된 객체와 배열을 저장하는 데 사용됩니다. 힙에 할당 된 메모리는 Java Virtual Machine의 자동 쓰레기 수집기에 의해 관리됩니다.
힙에 배열 또는 객체가 생성 된 후 스택에서 특수 변수를 정의 할 수 있으므로 스택 의이 변수의 값은 힙 메모리의 배열 또는 객체의 첫 번째 주소와 동일하고 스택의 변수는 어레이 또는 객체의 참조 변수가됩니다. 참조 변수는 배열 또는 객체에 주어진 이름과 동일합니다. 프로그램의 스택의 참조 변수를 사용하여 힙의 어레이 또는 객체에 액세스 할 수 있습니다. 참조 변수는 배열 또는 객체에 주어진 이름과 동일합니다.
기준 변수는 정의 될 때 스택에 할당 된 일반 변수입니다. 프로그램이 범위를 벗어난 후에 참조 변수가 릴리스됩니다. 배열과 물체 자체는 힙에 할당됩니다. 프로그램이 코드 블록 외부에서 실행 되더라도 배열 또는 객체를 생성하기 위해 새로운 명령문이 배열되어 있으면 배열 및 객체 자체가 차지하는 메모리가 해제되지 않습니다. 배열과 물체는 참조 변수가 가리키는 경우에만 쓰레기가되고 사용될 수 없지만 여전히 메모리 공간을 차지합니다. 그것은 불확실한 시간에 쓰레기 수집기에 의해 수집 (릴리스)됩니다. 이것이 또한 Java가 더 많은 기억을 취하는 이유이기도합니다.
실제로, 스택의 변수는 힙 메모리의 변수를 가리 킵니다. 이는 Java의 포인터입니다!
힙과 스택
Java의 힙은 객체가 공간을 할당하는 런타임 데이터 영역입니다. 이 객체는 New, NewArray, anewarray 및 Multianewarray와 같은 지침을 통해 설정됩니다. 그들은 프로그램 코드를 명시 적으로 릴리스 할 필요가 없습니다. 힙은 쓰레기 수거를 담당합니다. 힙의 장점은 메모리 크기를 동적으로 할당 할 수 있고 런타임에 메모리를 동적으로 할당하기 때문에 컴파일러를 미리 알릴 필요가 없다는 것입니다. Java의 쓰레기 수집기는 더 이상 사용되지 않은 데이터를 자동으로 수집합니다. 그러나 단점은 런타임에 메모리를 동적으로 할당해야하기 때문에 액세스 속도가 느리다는 것입니다.
스택의 장점은 액세스 속도가 힙보다 빠르고 레지스터에 이어 두 번째로 스택 데이터를 공유 할 수 있다는 것입니다. 그러나 단점은 스택의 데이터 크기와 수명이 결정적이어야하고 유연성이 부족하다는 것입니다. 스택은 주로 기본 유형의 가변 데이터 (int, short, long, byte, float, double, boolean, char) 및 객체 핸들 (참조)을 저장합니다.
스택의 매우 중요한 특수 기능은 스택에 존재하는 데이터를 공유 할 수 있다는 것입니다. 우리가 동시에 정의한다고 가정합니다.
자바 코드
int a = 3;
int b = 3;
컴파일러는 먼저 int a = 3을 처리합니다. 먼저 변수 a로 스택에 참조를 생성 한 다음 스택에 3의 값이 있는지 확인합니다. 발견되지 않으면 3을 저장 한 다음 A에서 3에서 3을 가리킨 다음 int B = 3을 처리합니다. 스택에 이미 3의 값이 있기 때문에 B의 기준 변수를 생성 한 후 B는 직접 3으로 가리 킵니다. 이러한 방식으로 A와 B는 동시에 3을 가리 킵니다.
현재 A = 4가 다시 설정된 경우; 그런 다음 컴파일러가 스택에 4 값이 있는지 다시 검색합니다. 그렇지 않은 경우, 4와 지점을 저장하십시오. 이미 존재하는 경우이 주소를 직접 지적하십시오. 따라서 값 A의 변화는 값에 영향을 미치지 않습니다. b.
이 데이터 공유는 동시에 하나의 객체를 가리키는 두 객체의 참조 공유와 다릅니다.이 경우 A의 수정은 B에 영향을 미치지 않기 때문에 공간을 절약하는 데 도움이되는 컴파일러에 의해 수행되기 때문에이 데이터의 수정은 B에 영향을 미치지 않기 때문입니다. 객체 참조 변수는이 객체의 내부 상태를 수정하고 다른 객체 참조 변수에 영향을 미칩니다.
자바 코드
1.int i1 = 9;
2. INT I2 = 9;
3. INT I3 = 9;
4. 공개 정적 최종 int1 = 9;
5. 공개 정적 최종 int2 = 9;
6. 공개 정적 최종 int3 = 9;
멤버 변수 및 로컬 변수의 경우 : 멤버 변수는 메소드 내부와 클래스 내부에 정의 된 변수입니다. 로컬 변수는 메소드 또는 명령문 블록 내부에 정의 된 변수입니다. 로컬 변수는 초기화되어야합니다.
공식 매개 변수는 로컬 변수이며 로컬 변수의 데이터는 스택 메모리에 존재합니다. 스택 메모리의 로컬 변수는 메소드가 사라지면 사라집니다.
멤버 변수는 힙의 물체에 저장되며 쓰레기 수집기에 의해 수집됩니다.
다음 코드에서와 같이 :
자바 코드
클래스 생년월일 {private int day; 개인 INT의 달; 개인 int 년; 공개 생년월일 (int d, int m, int y) {day = d; 달 = m; 년 = y; . 테스트 테스트 = New Test (); test.change (날짜); 생년월일 D1 = 새로운 생년월일 (7,7,1970); } public void Change1 (int i) {i = 1234; }위의 코드의 경우 날짜는 로컬 변수, I, D, M, Y는 모두 로컬 변수와 같은 공식적인 매개 변수이며, 일, 월 및 연도는 회원 변수입니다. 코드 실행 중 변경 사항을 분석하겠습니다.
1. 주요 방법은 실행을 시작합니다 : int date = 9;
날짜 로컬 변수, 기본 유형, 참조 및 값은 모두 스택에 있습니다.
2. 테스트 테스트 = New Test ();
테스트는 객체 참조이며 스택에 존재하며 객체 (New Test ())가 힙에 존재합니다.
3. test.change (날짜);
나는 로컬 변수이며, 기준과 값은 스택에 있습니다. 메소드 변경이 실행되면 스택에서 사라집니다.
4. Birthdate D1 = New Birthdate (7,7,1970);
D1은 객체 참조이며 스택에 존재합니다. 객체 (새 BirthDate ())는 힙에 존재하며, 여기서 d, m, y는 스택에 저장된 로컬 변수이고 유형은 기본 유형이므로 데이터는 스택에 저장됩니다. 요일, 월, 연도는 회원 변수이며 힙에 저장됩니다 (New Birthdate ()). 생년월일 생성자가 실행되면 D, M, Y는 스택에서 사라집니다.
5. 기본 방법이 실행되면 날짜 변수, 테스트 및 D1 참조가 스택에서 사라지고 새로운 test ()가 사라지면 새로운 BirthDate ()는 쓰레기 수집을 기다립니다.
끊임없는 수영장
상수 풀은 컴파일 기간 동안 결정되고 컴파일 된 .class 파일에 저장된 일부 데이터를 나타냅니다.
다양한 기본 유형 (int, long 등)의 상수 값 (최종)과 코드에 정의 된 객체 유형 (예 : 문자열 및 배열)을 포함하는 것 외에도 다음과 같은 텍스트 형식의 일부 상징적 참조도 포함됩니다.
◆ 자격을 갖춘 클래스 및 인터페이스 이름;
◆ 필드의 이름과 설명 자;
◆ 방법과 이름 및 설명자.
컴파일 기간이 생성 된 경우 (이중 인용구로 직접 정의 됨) 상수 풀에 저장되며 실행 기간 (신규)에 의해 결정될 수 있다면 힙에 저장됩니다. 평등 한 줄의 경우, 상수 풀에는 항상 하나의 사본과 힙에 다중 사본이 있습니다.
문자열은 특수 포장 데이터입니다. 사용할 수 있습니다 :
자바 코드
String str = new String ( "abc"); String str = "abc";
만들어야 할 두 가지 형식이 있습니다. 첫 번째는 새 ()를 사용하여 새 개체를 만드는 것입니다. 새 개체는 힙에 저장됩니다. 새 객체가 호출 될 때마다 생성됩니다. 두 번째 유형은 먼저 스택에서 문자열 클래스의 객체에 변수 str을 생성 한 다음 상징적 참조를 사용하여 문자열 상수 풀에 "ABC"가 있는지 확인하는 것입니다. 그렇지 않은 경우 "ABC"를 문자열 상수 풀에 보관하고 Str가 "ABC"를 가리 키게하십시오. 이미 "ABC"가 있다면 Str가 직접 "ABC"를 가리 키게하십시오.
클래스의 값이 동일인지 비교할 때 equals () 메소드를 사용하십시오. 두 래퍼 클래스의 참조가 동일한 객체를 가리키는 지 여부를 테스트 할 때 ==를 사용하고 아래의 이론을 설명하기 위해 아래 예제를 사용하십시오.
자바 코드
문자열 str1 = "abc"; String str2 = "abc"; system.out.println (str1 == str2); //진실
str1과 str2가 동일한 물체를 가리킨다는 것을 알 수 있습니다.
자바 코드
문자열 str1 = new String ( "abc"); String str2 = new String ( "abc"); System.out.println (str1 == str2); // 거짓
새로운 방법은 다른 객체를 생성하는 것입니다. 한 번에 하나씩 생성하십시오.
따라서, 두 번째 방식으로, 다중 "ABC"문자열이 생성되며 메모리에는 하나의 객체 만 있습니다. 이 글쓰기 방법은 유익하고 메모리 공간을 절약합니다. 동시에 JVM은 스택에서 데이터의 실제 상황을 기반으로 새 개체를 생성 해야하는지 자동으로 결정하기 때문에 프로그램의 실행 속도를 어느 정도 향상시킬 수 있습니다. 문자열 코드 str = new String ( "ABC");
반면에, 참고 : String str = "abc"와 같은 형식을 사용하여 클래스를 정의 할 때, 우리는 항상 String 클래스의 객체 str을 생성 할 수 있다고 당연한 것으로 여깁니다. 함정에 대해 걱정하십시오! 객체가 만들어지지 않았을 수도 있습니다! 그리고 아마도 이전에 만들어진 객체를 가리킬 수도 있습니다. 새로운 () 메소드를 통해서만 새 개체가 매번 생성되도록 할 수 있습니다.
문자열 상수 풀링 문제의 몇 가지 예
Example 1:
자바 코드
문자열 s0 = "kvill"; 문자열 s1 = "kvill"; 문자열 s2 = "kv" + "ill"; system.out.println (s0 == s1); system.out.println (s0 == s2); 결과는 : Truetrue입니다
분석 : 우선, 결과는 Java라는 것을 알아야합니다. String Constant에 사본이 하나뿐입니다.
예제에서 S0 및 S1은 둘 다 문자열 상수이기 때문에 컴파일 기간 동안 결정되므로 S0 == S1은 참입니다. "KV"와 "Ill"도 문자열 상수입니다. 문자열이 다중 문자열 상수에 의해 연결되면 분명히 문자열 상수 자체이므로 S2는 컴파일 기간 동안 문자열 상수로 구문 분석되므로 S2는 또한 상수 풀에서 "Kvill"에 대한 참조입니다. 그래서 우리는 s0 == S1 == S2를 얻습니다.
Example 2:
예:
자바 코드
분석 : 새 String ()로 생성 된 문자열은 상수가 아니며 컴파일 기간 동안 결정할 수 없으므로 New String ()에 의해 생성 된 문자열은 상수 풀에 배치되지 않으며 자체 주소 공간이 있습니다.
S0은 또한 상수 수영장에서 "Kvill"을 적용하는 것입니다. 컴파일 기간 동안 S1을 결정할 수 없으므로 런타임에 생성 된 새로운 객체 "kvill"에 대한 참조입니다. S2는 새로운 문자열의 후반기 ( "ill")를 갖기 때문에 컴파일 기간 동안 결정할 수 없으므로 새로 생성 된 객체 "kvill"의 적용이기도합니다. 이것을 이해하면이 결과가 얻는 이유를 알게 될 것입니다.
Example 3:
자바 코드
문자열 a = "a1"; 문자열 b = "a" + 1; system.out.println ((a == b)); // result = true String a = "true"; String b = "a" + "true"; System.out.println ((a == b)); // result = true 문자열 a = "a3.4"; 문자열 b = "a" + 3.4; system.out.println ((a == b)); // result = true
분석 : 문자열 상수의 JVM 연결을 위해 JVM은 프로그램 컴파일 기간 후 상수 문자열의 "+"연결을 연결된 값에 최적화합니다. "a" + 1을 예로 들어 보겠습니다. 컴파일러에 의한 최적화 후 클래스에서 이미 A1입니다. 컴파일 기간 동안 문자열 상수의 값이 결정되므로 위 프로그램의 최종 결과는 사실입니다.
Example 4:
자바 코드
문자열 a = "ab"; 문자열 bb = "b"; 문자열 b = "a" + bb; system.out.println ((a == b)); // result = false
분석 : JVM의 문자열 참조의 경우, 문자열의 " +"연결에 문자열 참조가 있기 때문에, 프로그램 컴파일 기간 동안 참조 된 값을 결정할 수 없으며, "A" + BB는 컴파일러에 의해 최적화 될 수 없으며 프로그램 실행 기간 동안 연결된 새 주소를 동적으로 할당하고 할당합니다. 따라서 위의 프로그램의 결과는 거짓입니다.
5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제 5 : 예제.
자바 코드
문자열 a = "ab"; 최종 문자열 bb = "b"; 문자열 b = "a" + bb; system.out.println ((a == b)); // result = true
분석 : [4]의 유일한 차이점은 BB 문자열이 최종 수정으로 장식된다는 것입니다. 최종 수정 된 변수의 경우, 컴파일 시간에 상수 값의 로컬 사본으로 구문 분석되고 자체 상수 풀에 저장되거나 바이트 코드 스트림에 포함됩니다. 따라서 현재 "A" + BB 및 "A" + "B"의 효과는 동일합니다. 따라서 위의 프로그램의 결과는 사실입니다.
예제 6 :
자바 코드
문자열 a = "ab"; 최종 문자열 bb = getbb (); 문자열 b = "a" + bb; system.out.println ((a == b)); // result = falsePrivate static String getBB () {return "b"; }분석 : JVM은 문자열에 대한 BB를 참조하며 컴파일 기간 동안 그 값을 결정할 수 없습니다. 프로그램 런타임 중 메소드를 호출 한 후에 만 메소드와 "A"의 리턴 값이 동적으로 연결되고 주소가 b에 할당됩니다. 따라서 위의 프로그램의 결과는 거짓입니다.
문자열은 불변입니다
위의 예에서 다음을 알 수 있습니다.
문자열 s = "a" + "b" + "c";
문자열 s = "abc"와 동일합니다.
문자열 a = "a";
문자열 b = "b";
문자열 c = "c";
문자열 s = a + b + c;
이것은 다릅니다. 최종 결과는 다음과 같습니다.
자바 코드
StringBuffer temp = new StringBuffer (); temp.append (a) .append (b) .append (c); String s = temp.tostring ();
위의 분석 결과에서 문자열이 연결 연산자 (+)를 사용 하여이 코드와 같은 비 효율성의 이유를 분석한다고 추론하는 것은 어렵지 않습니다.
자바 코드
공개 클래스 테스트 {public static void main (String args []) {String s = null; for (int i = 0; i <100; i ++) {s+= "a"; }}}+가 완료 될 때마다 StringBuilder 객체가 생성 된 다음 추가되어 버립니다. 다음에 루프가 도착하면 StringBuilder 객체가 재생 된 다음 문자열을 추가하고 루프가 끝날 때까지 완료됩니다. StringBuilder 객체를 직접 사용하여 추가하면 객체를 생성하고 파괴하기 위해 n -1 시간을 절약 할 수 있습니다. 따라서 루프에서 문자열 연결이 필요한 애플리케이션의 경우, 부속 작업은 일반적으로 StringBuffer 또는 StringBulider 객체를 사용하여 수행됩니다.
문자열 클래스의 불변의 특성으로 인해 이것에 대해 할 말이 많습니다. 문자열 인스턴스가 생성되면 변경되지 않는 한 다음과 같습니다. 4 개의 문자열 상수가 있습니다. 먼저, "KV"및 "ill"은 메모리에서 "Kvill"을 생성 한 다음 "Kvill"및 "" "" "Kvill"및 "Kvill Ans"를 생성 하고이 문자열의 주소가 STR에 할당됩니다. String의 "Emmutable"은 많은 임시 변수를 생성하기 때문에 StringBuffer를 사용하는 것이 권장되기 때문에 많은 임시 변수가 생성되기 때문입니다.
문자열의 최종 사용 및 이해
자바 코드
최종 StringBuffer A = New StringBuffer ( "111"); 최종 StringBuffer B = New StringBuffer ( "222"); A = B; //이 문장은 완성 될 때까지 컴파일하지 않습니다. 최종 StringBuffer A = New StringBuffer ( "111"); A.Append ( "222"); /// 완료된 후 컴파일
최종은 참조 된 "값"(즉, 메모리 주소)에만 유효하다는 것을 알 수 있습니다. 그것은 처음에 가리키는 물체 만 가리키도록 참조를 강요합니다. 포인팅을 변경하면 컴파일 타임 오류가 발생합니다. 객체의 변화에 관해서는, 결승전은 무책임합니다.
요약
스택은 원래 데이터 유형의 일부 로컬 변수 데이터를 저장하는 데 사용되며 개체 (문자열, 배열, 개체 등)에 대한 참조이지만 객체 내용을 저장하지 않습니다.
새 키워드를 사용하여 생성 된 객체는 힙에 저장됩니다.
문자열은 특수 래퍼 클래스이며, 참조는 스택에 저장되며 객체 내용은 생성 방법 (상수 풀 및 힙)에 따라 결정해야합니다. 일부는 컴파일 시간에 생성되고 문자열 상수 풀에 저장되며 다른 일부는 런타임에만 생성됩니다. 새 키워드를 사용하고 힙에 저장하십시오.
위의 기사는 Java+ 메모리 할당과 가변 저장 위치의 차이점에 대해 간략하게 이야기합니다. 나는 당신이 당신에게 참조를 줄 수 있기를 바랍니다. 그리고 당신이 wulin.com을 더 지원할 수 있기를 바랍니다.