Java에는 두 가지 형태의 문자열 객체 생성이 있는데, 하나는 String str = "droid";와 같은 리터럴 형태이고, 다른 하나는 String str = new String(와 같은 객체 생성의 표준 방법인 new를 사용하는 것입니다. " droid"); 이 두 가지 방법은 코드를 작성할 때, 특히 리터럴 방법을 작성할 때 자주 사용됩니다. 그러나 실제로 이 두 구현 간에는 성능과 메모리 사용량에 약간의 차이가 있습니다. 이 모든 것은 문자열 객체의 반복 생성을 줄이기 위해 JVM이 문자열 상수 풀 또는 문자열 리터럴 풀이라고 하는 특수 메모리를 유지하기 때문입니다.
작동 원리
코드에서 문자열 객체가 리터럴 형식으로 생성되면 JVM은 먼저 리터럴을 확인합니다. 문자열 상수 풀에 동일한 내용을 가진 문자열 객체에 대한 참조가 있으면 해당 참조가 반환됩니다. 새 문자열이 생성됩니다. 객체가 생성되고 이 참조가 문자열 상수 풀에 저장되고 참조가 반환됩니다.
예를 들어보세요
리터럴 생성 형태
다음과 같이 코드 코드를 복사합니다.
문자열 str1 = "드로이드";
JVM은 이 리터럴을 감지합니다. 여기서는 내용이 droid인 개체가 없다고 생각합니다. JVM은 문자열 상수 풀을 통해 droid의 내용이 포함된 문자열 개체의 존재를 찾을 수 없습니다. 그런 다음 문자열 개체를 생성한 다음 새로 생성된 개체의 참조를 문자열 상수 풀에 넣고 참조를 반환합니다. 변수 str1 .
다음에 이런 코드가 있다면
다음과 같이 코드 코드를 복사합니다.
문자열 str2 = "드로이드";
마찬가지로, JVM은 여전히 이 리터럴을 감지해야 합니다. JVM은 문자열 상수 풀을 검색하고 "droid" 내용이 포함된 문자열 개체가 존재하는지 확인하므로 기존 문자열 개체의 참조를 변수 str2에 반환합니다. 여기서는 새 문자열 개체가 다시 생성되지 않습니다.
str1과 str2가 동일한 객체를 가리키는지 확인하려면 다음 코드를 사용할 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
System.out.println(str1 == str2);
결과는 사실입니다.
새로운 것을 사용하여 생성
다음과 같이 코드 코드를 복사합니다.
String str3 = new String("droid");
new를 사용하여 문자열 개체를 생성하면 문자열 상수 풀에 동일한 내용을 가진 개체에 대한 참조가 있는지 여부에 관계없이 새 문자열 개체가 생성됩니다. 그래서 우리는 그것을 테스트하기 위해 다음 코드를 사용합니다.
다음과 같이 코드 코드를 복사합니다.
String str3 = new String("droid");
System.out.println(str1 == str3);
결과는 우리가 생각한 대로 거짓이며, 이는 두 변수가 서로 다른 객체를 가리킨다는 것을 나타냅니다.
인턴
위의 new를 사용하여 생성된 문자열 개체에 대해 이 개체의 참조를 문자열 상수 풀에 추가하려면 intern 메서드를 사용하면 됩니다.
intern을 호출한 후 먼저 문자열 상수 풀에 개체에 대한 참조가 있는지 확인하고, 존재하는 경우 해당 참조를 변수에 반환하고, 그렇지 않으면 참조를 추가하여 변수에 반환합니다.
다음과 같이 코드 코드를 복사합니다.
문자열 str4 = str3.intern();
System.out.println(str4 == str1);
출력 결과는 true입니다.
어려운 질문
전제 조건?
문자열 상수 풀을 구현하기 위한 전제 조건은 Java의 문자열 개체가 불변이어서 여러 변수가 동일한 개체를 공유하도록 안전하게 보장할 수 있다는 것입니다. Java의 String 개체가 변경 가능하고 참조 작업으로 인해 개체의 값이 변경되면 다른 변수도 영향을 받게 됩니다.
참조 또는 객체
가장 일반적인 문제는 참조 또는 개체가 문자열 상수 풀에 저장되어 있는지 여부입니다. 문자열 상수 풀은 객체가 아닌 객체 참조를 저장합니다. Java에서는 객체가 힙 메모리에 생성됩니다.
업데이트 확인, 많은 댓글에서도 이 문제에 대한 논의가 있어 간단히 확인했습니다. 검증 환경:
다음과 같이 코드 코드를 복사합니다.
22:18:54-androidyue~/Videos$ 고양이 /etc/os-release
이름=페도라
VERSION="17 (엄청난 기적)"
아이디=페도라
VERSION_ID=17
PRETTY_NAME="Fedora 17(두툼한 기적)"
ANSI_COLOR="0;34"
CPE_NAME="cpe:/o:fedoraproject:fedora:17"
22:19:04-androidyue~/Videos$ java -version
자바 버전 "1.7.0_25"
OpenJDK 런타임 환경(fedora-2.3.12.1.fc17-x86_64)
OpenJDK 64비트 서버 VM(빌드 23.7-b01, 혼합 모드)
검증 아이디어: 다음 Java 프로그램은 82M 크기의 비디오 파일을 읽고 문자열 형태로 인턴 작업을 수행합니다.
다음과 같이 코드 코드를 복사합니다.
22:01:17-androidyue~/Videos$ ll -lh grep Why_to_learn.mp4 |
-rw-rw-r--.1 androidyue androidyue 82M 2013년 10월 20일 Why_to_learn.mp4
인증코드
다음과 같이 코드 코드를 복사합니다.
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
공개 클래스 TestMain {
개인 정적 문자열 fileContent;
공개 정적 무효 메인(String[] args) {
fileContent = readFileToString(args[0]);
if (null != fileContent) {
fileContent = fileContent.intern();
System.out.println("Null이 아님");
}
}
개인 정적 문자열 readFileToString(문자열 파일) {
BufferedReader 리더 = null;
노력하다 {
reader = new BufferedReader(새 FileReader(파일));
StringBuffer 버프 = 새로운 StringBuffer();
스트링라인;
while ((line = reader.readLine()) != null) {
buff.append(라인);
}
return buff.toString();
} 잡기(FileNotFoundException e) {
e.printStackTrace();
} 잡기(IOException e) {
e.printStackTrace();
} 마지막으로 {
if (null != 리더) {
노력하다 {
reader.close();
} 잡기(IOException e) {
e.printStackTrace();
}
}
}
null을 반환;
}
}
문자열 상수 풀은 힙 메모리의 영구 생성에 존재하므로 Java8 이전에도 적용 가능합니다. 영구 생성을 매우 작은 값으로 설정하여 이를 확인했습니다. 문자열 객체가 문자열 상수 풀에 존재하는 경우 java.lang.OutOfMemoryError permgen 공간 오류가 필연적으로 발생합니다.
다음과 같이 코드 코드를 복사합니다.
java -XX:PermSize=6m TestMain ~/Videos/why_to_learn.mp4
증명 프로그램을 실행해도 OOM이 발생하지 않았습니다. 실제로 개체 또는 참조가 저장되어 있는지 잘 증명할 수 없습니다.
그러나 이는 적어도 문자열의 실제 콘텐츠 개체 char[]가 문자열 상수 풀에 저장되지 않았음을 증명합니다. 이 경우 문자열 상수 풀이 문자열 개체를 저장하는지 아니면 문자열 개체에 대한 참조를 저장하는지 여부는 실제로 그다지 중요하지 않습니다. 그러나 개인적으로는 여전히 참조용으로 저장하는 것을 선호합니다.
장점과 단점
문자열 상수 풀의 장점은 동일한 내용의 문자열 생성을 줄이고 메모리 공간을 절약한다는 것입니다.
굳이 단점을 꼽자면, 공간 대신 CPU 컴퓨팅 시간이 희생된다는 점이다. CPU 계산 시간은 주로 문자열 상수 풀에 동일한 내용을 가진 객체에 대한 참조가 있는지 확인하는 데 사용됩니다. 그러나 내부 구현은 HashTable이므로 계산 비용이 저렴합니다.
GC 재활용?
문자열 상수 풀은 공유 문자열 개체에 대한 참조를 보유하므로 이러한 개체를 재활용할 수 없다는 의미입니까?
우선, 문제의 공유 객체는 일반적으로 상대적으로 작습니다. 내가 아는 한, 이 문제는 이전 버전에도 존재했지만 약한 참조가 도입되면서 이제 이 문제는 사라졌습니다.
이 문제와 관련하여 인턴된 문자열: Java 용어집 문서에 대해 자세히 알아볼 수 있습니다.
인턴 사용?
인턴을 사용하기 위한 전제조건은 실제로 인턴을 사용해야 한다는 것을 아는 것입니다. 예를 들어, 여기에 수백만 개의 레코드가 있고 레코드의 특정 값이 미국 캘리포니아인 경우가 많습니다. 우리는 이러한 문자열 객체를 수백만 개 생성하여 메모리에 단 하나의 복사본만 보관하고 싶지 않습니다. 할 수 있다. 인턴에 대한 보다 심층적인 이해를 위해서는 String#intern 심층 분석을 참조하시기 바랍니다.
항상 예외가 있나요?
다음 코드가 여러 문자열 개체를 생성하고 문자열 상수 풀에 여러 참조를 저장한다는 것을 알고 계셨습니까?
다음과 같이 코드 코드를 복사합니다.
문자열 테스트 = "a" + "b" + "c";
정답은 단 하나의 객체만 생성되고 단 하나의 참조만 상수 풀에 저장된다는 것입니다. javap를 사용하여 디컴파일하고 살펴보면 알 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
17:02 $ javap -c TestInternedPoolGC
"TestInternedPoolGC.java"에서 컴파일됨
공용 클래스 TestInternedPoolGC는 java.lang.Object를 확장합니다.
공개 TestInternedPoolGC();
암호:
0: 로드_0
1: Invokespecial #1; //메소드 java/lang/Object."<init>":()V
4: 복귀
public static void main(java.lang.String[])이 java.lang.Exception을 발생시킵니다.
암호:
0: ldc #2; //문자열 abc;
2: astore_1
3: 복귀
컴파일하는 동안 이 세 가지 리터럴이 하나로 결합된 것을 보셨나요? 이는 실제로 중복 문자열 개체 생성을 방지하고 문자열 연결 문제를 일으키지 않는 최적화입니다. 문자열 접합과 관련하여 Java 세부 정보(문자열 접합)를 볼 수 있습니다.