인터넷의 주요 섹션에서 실행될 때 Java 문자열의 메모리 할당에 대한 토론을 종종 다음과 같이 볼 수 있습니다 : 문자열 a = "123", 문자열 b = 새 문자열 ( "123"),이 두 형태의 문자열은 어디에 저장됩니까? 실제로,이 두 형태의 문자열 "123"의 문자 값은 스택에 또는 런타임에 힙에 저장되지 않습니다. 방법 영역의 특정 상수 영역에 저장되며 동일한 문자열 리터럴 값에 대해 하나의 사본 만 메모리에 유지됩니다. 아래 예제로 분석하겠습니다.
1. == 연산자는 두 개의 문자열 참조 비교가 비교되는 두 가지 경우에 사용됩니다.
public class stringtest {public static void main (String [] args) {// 1 부 s1 = "나는 중국을 사랑합니다"; 문자열 s2 = "나는 중국을 사랑한다"; System.out.println ( "결과 :" + s1 == s2); // 프로그램 실행 결과는 true // part 2 String s3 = new String ( "I Love China"); 문자열 s4 = 새 문자열 ( "나는 중국을 사랑한다"); system.out.println ( "결과 :" + s3 == s4); // 프로그램 실행 결과는 false}}}Java의 == 운영자는 변수 값을 비교한다는 것을 알고 있습니다. 참조 유형에 해당하는 변수의 값은 참조 객체의 주소를 저장합니다. 여기서 문자열은 참조 유형이며 여기에서 네 가지 변수의 값은 실제로 문자열의 주소를 저장합니다. 새 연산자가 JVM이 런타임에 힙에 새 개체를 생성하고 두 개의 다른 객체의 주소가 다르기 때문에 Part2의 실행 결과는 분명합니다. 그러나 Part1의 실행 결과로부터 S1 및 S2가 동일한 주소가 지적된다는 것을 알 수 있습니다. 그렇다면 S1과 S2 변수에 의해 문자열이 어디에 지적됩니까? JVM은 문자열을 어떻게 처리합니까? 마찬가지로, 변수 S3 및 S4를 가리키는 힙의 다른 문자열 객체의 경우, 자체 객체 공간에 "I Love China"문자열을 저장합니까? JVM이 문자열을 처리하는 방법을 이해하기 위해 먼저 Java 컴파일러에서 생성 한 바이트 코드 지침을 살펴 봅니다. 바이트 코드 명령을 통해 JVM이 수행 할 작업을 분석합니다.
2. 다음은 프로그램에서 생성 된 바이트 코드 정보입니다. 빨간색은 우리가주의를 기울여야 할 부분을 표시합니다.
상수 풀 : #1 = 클래스 #2 // StringTest #2 = UTF8 StringTest #3 = 클래스 #4 // Java/Lang/Object #4 = UTF8 Java/Lang/Object #5 = UTF8 <Init> #6 = UTF8 () V #7 = UTF8 코드 #8 = MethodRef #3. #9 // java/object. nameandtype #5 : #6 // "<init>":() V #10 = UTF8 LinEnumberTable #11 = UTF8 LocalVariableTable #12 = UTF8이 #13 = UTF8 lstringStest; #14 = UTF8 메인 #15 = UTF8 ([ljava/lang/string;) v #16 = 문자열 #17 // 나는 문자열 주소에 대한 중국 참조 #17 = UTF8 I Love China #18 = FieldRef #19. #21 // java/lang/system.out : ljava/io/pintstream; #19 = 클래스 #20 // Java/Lang/System #20 = UTF8 Java/Lang/System #21 = NameandType #22 : #23 // Out : Ljava/IO/PrintStream; #22 = UTF8 OUT #23 = UTF8 LJAVA/IO/PRINTSTREAM; #24 = 클래스 #25 // Java/Lang/StringBuilder #25 = UTF8 Java/Lang/StringBuilder #26 = String #27 // 결과 : #27 = UTF8 결과 : #28 = MethodRef #24. #29 // java/lang/StringBuilder. "<init>":( ljava/lang/lang/lang # # # #23 # #33 #33. // "<init>":( ljava/lang/string;) v#30 = utf8 (ljava/lang/string;) v#31 = MethodRef#24.#32 // java/lang/stringbuilder.append : (z) ljava/lang/stringbuilder;#32 = nameandtype#34 // (Z) ljava/lang/stringbuilder;#33 = UTF8 부록#34 = UTF8 (Z) ljava/lang/lang/stringBuilder;#35 = MethodRef#24.#36 // java/lang/stringBuilder.toString :() ljava/lang/string; TOSTRING :() ljava/lang/string;#37 = utf8 tostring#38 = utf8 () ljava/lang/string;#39 = MethodRef#40.#42 //#42 // java/io/printstream.println : (ljava/lang/string;) V#40 = Class#41 // java/printtream#41. UTF8 JAVA/IO/PRINTSTREAM#42 = NAMEANDTYPE#43 :#30 // println : (ljava/lang/string;) v#43 = utf8 println#44 = 클래스#45 // java/lang/string#45 = utf8 java/lang/string#46 = 44.#29///#29 // Java/Lang/String. "<init>":( ljava/lang/string;) v#47 = UTF8 Args#48 = UTF8 [ljava/lang/string;#49 = UTF8 S1#50 = UTF8 ljava/lang/string;#51 = UTF8 S2#52 = UTF8 S3#5#54#54#54#54#54 = UTF8 stackMaptable#55 = 클래스#48 // "[ljava/lang/string;"#56 = UTF8 SourceFile#57 = UTF8 StringTest.java ............ // 해당 메소드의 바이트 코드 명령은 JVM 런타임에 의해 설명되고 실행됩니다. public static void main (java.lang.string []); 디스크립터 : ([ljava/lang/string;) v 플래그 : Acc_public, Acc_static 코드 : stack = 4, locals = 5, args_size = 1 0 : ldc #16 // string 중국을 좋아합니다. 이 명령어는 다음 명령어 2에 해당합니다. 문자열 S1 = "I Love China"진술서 2 : Store_1 // 스택 상단에있는 객체 참조를 로컬 변수 1. 3 : LDC #16 // String I Love China에 동일한 기호 참조에서 상수로 지적합니다. 이 명령 및 다음 명령 5는 프로그램에서 문자열 S2 = "I Love China"진술에 해당합니다. 5 : store_2 // 스택 상단의 로컬 변수에 대한 객체 참조 지정 2. 6 : getstatic #18 // Field Java/Lang/System.out : ljava/io/printstream; 9 : 새로운 #24 // 클래스 java/lang/stringbuilder 12 : dup 13 : LDC #26 // 문자열 결과 : 15 : invokescial #28 // 메소드 Java/lang/StringBuilder. "<init>":( ljava/lang/string;) v 18 : aload_1 19 : aload_2 20 : if_acmpne // pop on the the the the tog the the tog the the tog the tog the the tokmpne. 동등한 지 여부를 비교하고, 지시문으로 이동하고, 실행, 다음 명령을 동일하게 실행합니다. 31 : InvokeVirtual #35 // 메소드 Java/Lang/StringBuilder.tostring :() ljava/lang/string; 34 : InvokeVirtual #39 // 메소드 java/io/printstream.println : (ljava/lang/string;) v 37 : new #44 // 클래스 java/lang/string, 객체를 만들고, 끊임없는 풀 #44의 참조에 위치한 문자열 객체가 있으며, 객체 참조는 스택 상단으로 밀려납니다. 40 : dup // 스택 상단에있는 객체의 사본을 복사하여 스택 상단으로 밀어 넣습니다. 41 : LDC #16 // String I Love China, 0, 3 지침과 동일합니다. 43 : invokescial #46 // 메소드 Java/lang/String. "<init>":( ljava/lang/string;) v 46 : store_3 47 : new #44 // 클래스 Java/Lang/String // 개체를 만들고 스택의 상단에 객체를 생성하고 스탠드를 사랑합니다. 53 : invokescial #46 // 메소드 java/lang/string. "<init>":( ljava/lang/string;) v, 스택의 상단과 문자열 참조에서 해당 객체 참조에 따라 객체의 초기화 메소드를 호출하고 문자열 객체를 초기화하고 46 : 객체 참조를 변수 4. Java/Lang/System.out : ljava/io/printstream; 61 : 새로운 #24 // 클래스 Java/Lang/StringBuilder 64 : DUP 65 : LDC #26 // 문자열 결과 : 67 : invokespecial #28 // 메소드 java/lang/stringbuilder.append : (z) ljava/lang/stringbuilder; 84 : InvokeVirtual #35 // 메소드 Java/Lang/StringBuilder.toString :() ljava/lang/StringBuilder.Append : (Z) ljava/lang/StringBuilder; 84 : InvokeVirtual #35 // 메소드 java/lang/stringbuilder.tostring :() ljava/lang/stringbuilder; 87 : InvokeVirtual #39 // 메소드 Java/IO/PrintStream.println : (Ljava/lang/String;) v 90 : return ......... LinenumberTable : Line 7 : 0line 8 : 3line 9 : 6line 11 : 37line 12 : 47line 13 : 58line 14 : 90localvariabletable Starting Spigs 91 0 Argit 91 0 Arger [ljava/lang/string; // 로컬 변수 088 1 S1 ljava/lang/string; // 로컬 변수 185 2 S2 Ljava/Lang/String; // 로컬 변수 244 3 S3 Ljava/Lang/String; // 로컬 변수 333 4 S4 Ljava/Lang/String; // 로컬 변수 4
바이트 코드의 빨간색 부분은 우리의 토론과 관련이 있습니다. 생성 된 바이트 코드를 통해 예제 프로그램에 대한 다음 결론을 도출 할 수 있습니다.
1). Java 컴파일러가 프로그램을 바이트 코드로 컴파일하면 문자열 상수 "I Love China"는 먼저 바이트 코드 상수 풀에 존재하는지 여부를 결정합니다. 존재하지 않으면 생성되지 않습니다. 즉, 동일한 문자열이 예약되어 있습니다. 프로그램의 문자열 변수 S1 및 S2가 상수 풀에서 동일한 문자열 상수를 가리 키도록 상징적 참조를 통해서만 하나의 사본 만 찾을 수 있습니다. 런타임에 JVM은 일반적으로 상수 풀이라고하는 방법 영역의 위치에 바이트 코드 상수 풀에 문자열 상수를 저장하며 문자열은 문자 배열 형태의 인덱스를 통해 액세스됩니다. JVM은 런타임시 문자열의 실제 메모리 주소에 대해 S1과 S2를 가리키는 문자열의 상대 참조 주소를 가리 킵니다.
2). String S3 = New String ( "I Love China"), String S4 = New String ( "I Love China"), Bytecode에서 새 명령어를 호출하는 것을 볼 수 있습니다. JVM은 런타임에 두 개의 다른 객체를 생성하고 S3 및 S4는 다른 객체 주소를 가리 킵니다. 따라서 S3 == S4 비교 결과는 거짓입니다.
둘째, S3 및 S4 객체의 초기화를 위해, 바이트 코드에서 객체의 초기 방법이 호출되고 상수 풀에서 "I Love China"가 전달된다는 것을 알 수 있습니다. 그렇다면 문자열 객체의 생성 및 초기화는 무엇입니까? 문자열의 소스 코드와 문자열 객체에 의해 생성 된 바이트 코드를 확인하여 문자열이 객체 내부에서 복사되어 있는지 또는 문자열에 해당하는 상수 풀의 주소에 직접 참조되어 있는지 더 잘 이해할 수 있습니다.
3. 문자열 객체의 소스 코드의 일부 :
<span style = "font-size : 14pt"> public final class string은 java.io.serializable, bumpable <string>, charSequence { /** 값이 문자 저장에 사용됩니다. */ Private Final Char Value []; / ** 문자열*/ private int hash의 해시 코드를 캐시합니다. // 기본적으로 0 public String () {this.value = new char [0]; } </span> <span style = "back this.hash = original.hash; } </span>소스 코드에서 문자열 클래스에 인스턴스 변수 char value []가 있음을 알 수 있습니다. 시공 방법을 통해 초기화시 객체가 복사 작업을 수행하지 않고 전달 된 문자열 객체의 주소 참조 만 인스턴스 변수 값에 할당합니다. 이것으로부터 우리는 처음에 새 문자열 ( "ABC")을 사용하여 문자열 객체를 만들 때에도 메모리 힙의 객체에 대한 공간이 할당되지만 "ABC"자체에 대한 정보는 힙에 저장되지 않지만 "ABC"문자열에 대한 참조는 "ABC"문자열에 대한 인스턴스 내에서 초기화됩니다. 실제로 이것은 메모리 저장 공간을 절약하고 프로그램 성능을 향상시키는 것입니다.
4. 문자열 객체의 바이트 코드 정보를 살펴 보겠습니다.
public java.lang.string (); 디스크립터 : () v 플래그 : Acc_public 코드 : 스택 = 2, 지역 = 1, args_size = 1 0 : aload_0 1 : invokespecial #1 // 메소드 Java/lang/Object. "<init>":() v 4 : aload_0 5 : iconst_0 6 : newArray Char 8 : 0 value : [C11 : C11 : C11 : CO1 : CLOUARRAY # 138 : 4 라인 139 : 11 Public Java.Lang.string (java.lang.string); 디스크립터 : (ljava/lang/string;) v 플래그 : acc_public 코드 : stack = 2, locals = 2, args_size = 2 0 : aload_0 // 로컬 변수 0을 스택의 상단으로 푸시, 자체 객체에 대한 참조. 1 : invokescial #1 // 메소드 Java/Lang/Object. "<init>":() v 스택 상단 객체를 팝업하여 객체의 #1에서 초기화 메소드를 참조하십시오. 4 : aload_0 // 자체 객체의 참조를 스택 상단에 푸시합니다. 5 : aload_1 // 전달 된 문자열 참조는 스택 상단으로 푸시합니다. 6 : getfield #2 // 필드 값 : [C // 스택 상단에서 문자열 참조를 팝업하고 #2의 인스턴스 변수에 할당하고 스택에 저장하십시오. 9 : Putfield #2 // 필드 값 : [C // 스택 상단과 객체 자체의 문자열 참조를 팝업하고 문자열 참조를 객체 자체의 인스턴스 변수에 할당합니다. 12 : aload_0 13 : aload_1 14 : getfield #3 // 필드 해시 : i 17 : Putfield #3 // 필드 해시 : i 20 : 반환
바이트 코드의 관점에서, 우리는 새로운 문자열 ( "ABC")이 문자열을 복사하는 대신 새 개체를 구성 할 때 문자열 참조의 할당을 수행한다고 결론 지을 수 있습니다. 위는 소스 코드 및 바이트 코드의 관점에서 문자열의 메모리 할당에 대한 분석 및 요약입니다.
Java 문자열 메모리 할당 (권장)의 위의 분석 및 요약은 내가 공유하는 모든 내용입니다. 나는 당신이 당신에게 참조를 줄 수 있기를 바랍니다. 그리고 당신이 wulin.com을 더 지원할 수 있기를 바랍니다.