Java String Interning이란 무엇입니까?


234

무엇 문자열 인턴 자바 내가 그것을 사용하고,시기, 이유는 ?



2
만약 String a = new String("abc"); String b = new String("abc"); 다음a.intern() == b.intern()
Asanka Siriwardena

Checkout String interning
Ronak Poriya

비 음주자 String.intern()에 따라 ClassLoader의미 "다른"창조 다른 클래스 로더의 작업을 수행 String다른 원인들 intern들?
AlikElzin-kilaka

1
@ AlikElzin-kilaka 아니오, 클래스 로더는 문자열 인턴과 관련이 없습니다. 다음에 질문이 있으면 다른 질문에 대한 의견으로 새 질문 을 올리지 마십시오 .
Holger

답변:


233

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern ()

기본적으로 일련의 문자열에서 String.intern ()을 수행하면 내용이 같은 모든 문자열이 동일한 메모리를 공유합니다. 따라서 'john'이 1000 번 나타나는 이름 목록이있는 경우 interning을 통해 하나의 'john'만 실제로 메모리에 할당되도록합니다.

프로그램의 메모리 요구 사항을 줄이는 데 유용 할 수 있습니다. 그러나 캐시는 영구 메모리 풀에서 일반적으로 힙에 비해 크기가 제한된 JVM에 의해 유지되므로 중복 값이 ​​너무 많지 않은 경우 인턴을 사용하지 않아야합니다.


intern () 사용의 메모리 제약에 대한 추가 정보

한편으로, 문자열 중복을 내부화하여 제거 할 수있는 것은 사실입니다. 문제는 내부화 된 문자열이 Permanent Generation (영구 생성)으로 이동한다는 것입니다.이 클래스는 클래스, 메소드 및 기타 내부 JVM 오브젝트와 같이 사용자가 아닌 오브젝트를 위해 예약 된 JVM 영역입니다. 이 영역의 크기는 제한되어 있으며 일반적으로 힙보다 훨씬 작습니다. 문자열에서 intern ()을 호출하면 힙에서 영구 생성으로 이동하는 효과가 있으며 PermGen 공간이 부족할 위험이 있습니다.

- 출처 : http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html


JDK 7 (HotSpot에서 의미)에서 무언가가 변경되었습니다.

JDK 7에서 인턴 된 문자열은 더 이상 Java 힙의 영구 생성에 할당되지 않지만 대신 애플리케이션이 작성한 다른 오브젝트와 함께 Java 힙의 주요 부분 (젊고 오래된 세대)에 할당됩니다. . 이 변경으로 인해 기본 Java 힙에 더 많은 데이터가 상주하고 영구 생성의 데이터가 줄어들어 힙 크기를 조정해야 할 수 있습니다. 대부분의 응용 프로그램은 이러한 변경으로 인해 힙 사용량에서 비교적 작은 차이 만 볼 수 있지만 많은 클래스를로드하거나 String.intern () 메서드를 많이 사용하는 더 큰 응용 프로그램은 더 큰 차이를 보게됩니다.

-에서 자바 SE 7 개 기능 및 향상된 기능

업데이트 : 인턴 된 문자열은 Java 7부터 기본 힙에 저장됩니다. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes


1
"그러나 캐시는 일반적으로 크기가 제한된 영구 메모리 풀에서 JVM에 의해 유지된다는 점에 유의하십시오 ..." 설명 할 수 있습니까? 이해하지 못했습니다
saplingPro

2
"interned"문자열은 JVM의 특수 메모리 영역에 저장됩니다. 이 메모리 영역은 일반적으로 크기가 고정되어 있으며 다른 데이터가 저장되는 일반적인 Java 힙의 일부가 아닙니다. 고정 된 크기로 인해이 영구 메모리 영역이 모든 문자열로 채워 져서 문제가 발생할 수 있습니다 (클래스를로드 할 수없고 다른 항목이있을 수 있음).
첼로

@cello, 캐싱과 비슷합니까?
saplingPro

8
@grassPro : 예. 일종의 캐싱으로, JVM에서 기본적으로 제공합니다. 참고로, Sun / Oracle JVM과 JRockit의 병합으로 인해 JVM 엔지니어는 JDK 8의 영구 메모리 영역 ( openjdk.java.net/jeps/122 )을 제거하려고 시도 하지 않으므로 향후 모든 크기 제한.
첼로

9
프로그래머는 또한 문자열 인턴이 보안에 영향을 줄 수 있음을 알고 있어야합니다. 메모리에 문자열과 같은 비밀번호와 같은 민감한 텍스트가있는 경우 실제 문자열 객체가 오랫동안 GC 된 경우에도 매우 오랫동안 메모리에 남아있을 수 있습니다. 나쁜 사람이 어떻게 든 메모리 덤프에 액세스하면 문제가 될 수 있습니다. 이 문제는 GC가 시작하기에 결정적이지 않기 때문에 인턴 없이도 존재하지만 다소 악화됩니다. 민감한 텍스트 char[]대신에 사용하는 것이 좋으며 String더 이상 필요하지 않은 즉시 제거 하는 것이 좋습니다 .
chris

71

당신이 평등 해지는 이유와 같은 "가시적 인 인터뷰"질문 이 있습니다! 아래 코드를 실행하면

String s1 = "testString";
String s2 = "testString";
if(s1 == s2) System.out.println("equals!");

문자열을 비교하려면을 사용해야합니다 equals(). 위의 내용은 컴파일러 testString가 이미 당신을 위해 인턴 했기 때문에 동일하게 인쇄됩니다 . 이전 답변에서 볼 수 있듯이 인턴 방법을 사용하여 문자열을 인턴 할 수 있습니다 ....


5
당신의 예제는 까다로운 원인이므로이 equals방법 을 사용하더라도 동일한 인쇄 결과를 얻을 수 있습니다 . new String()구별을보다 명확하게 보여주기 위해 비교 를 추가 할 수 있습니다 .
giannis christofakis

@giannischristofakis 그러나 우리가 새로운 String ()을 사용한다면 == 실패하지 않습니까? java는 새로운 문자열을 자동으로 내재화합니까?
Deepak Selvakumar

@giannischristofakis 물론 새로운 String ()을 사용하면 ==에서 실패합니다. 그러나 new String (...). intern ()은 인턴이 동일한 문자열을 반환하기 때문에 ==에서 실패하지 않습니다. 컴파일러가 리터럴로 새로운 String (). intern을 수행한다고 가정하면
maslan

42

JLS

JLS 7 3.10.5가이를 정의하고 실용적인 예를 제공합니다.

또한 문자열 리터럴은 항상 String 클래스의 동일한 인스턴스를 나타냅니다. String.intern 메소드를 사용하여 문자열 리터럴 또는보다 일반적으로 상수 표현식 (§15.28)의 값인 문자열이 "인터 닝"되어 고유 한 인스턴스를 공유하기 때문입니다.

예 3.10.5-1. 문자열 리터럴

편집 단위 (§7.3)로 구성된 프로그램 :

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

그리고 컴파일 단위 :

package other;
public class Other { public static String hello = "Hello"; }

출력을 생성합니다.

true true true true false true

JVMS

JVMS 7 5.1에 따르면 인턴은 전용 CONSTANT_String_info구조체를 사용하여 마술처럼 효율적으로 구현됩니다 (일반적인 표현을 가진 대부분의 다른 객체와 달리).

문자열 리터럴은 String 클래스의 인스턴스에 대한 참조이며 클래스 또는 인터페이스의 이진 표현에서 CONSTANT_String_info 구조 (§4.4.3)에서 파생됩니다. CONSTANT_String_info 구조는 문자열 리터럴을 구성하는 일련의 유니 코드 코드 포인트를 제공합니다.

Java 프로그래밍 언어를 사용하려면 동일한 문자열 리터럴 (즉, 동일한 코드 포인트 시퀀스를 포함하는 리터럴)이 동일한 String 클래스 인스턴스 (JLS §3.10.5)를 참조해야합니다. 또한 String.intern 메소드가 문자열에서 호출되면 결과는 해당 문자열이 리터럴로 표시 될 경우 리턴되는 동일한 클래스 인스턴스에 대한 참조입니다. 따라서 다음 표현식은 true 값을 가져야합니다.

("a" + "b" + "c").intern() == "abc"

문자열 리터럴을 도출하기 위해 Java Virtual Machine은 CONSTANT_String_info 구조에서 제공하는 코드 포인트 시퀀스를 검사합니다.

  • CONSTANT_String_info 구조에 의해 제공된 것과 동일한 유니 코드 코드 포인트를 포함하는 String 클래스의 인스턴스에서 String.intern 메소드가 이전에 호출 된 경우, 문자열 리터럴 파생 결과는 동일한 클래스의 클래스 인스턴스에 대한 참조입니다.

  • 그렇지 않으면 CONSTANT_String_info 구조에 의해 제공된 일련의 유니 코드 코드 포인트를 포함하는 String 클래스의 새 인스턴스가 작성됩니다. 해당 클래스 인스턴스에 대한 참조는 문자열 리터럴 파생의 결과입니다. 마지막으로, 새로운 String 인스턴스의 인턴 메소드가 호출됩니다.

바이트 코드

인턴 작동을 확인하기 위해 OpenJDK 7 바이트 코드를 디 컴파일 해 봅시다.

디 컴파일하면 :

public class StringPool {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a);
        System.out.println(b);
        System.out.println(a == c);
    }
}

우리는 상수 수영장에 있습니다.

#2 = String             #32   // abc
[...]
#32 = Utf8               abc

그리고 main:

 0: ldc           #2          // String abc
 2: astore_1
 3: ldc           #2          // String abc
 5: astore_2
 6: new           #3          // class java/lang/String
 9: dup
10: ldc           #2          // String abc
12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne     42
38: iconst_1
39: goto          43
42: iconst_0
43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V

참고 사항 :

  • 03: 동일한 ldc #2상수로드 (리터럴)
  • 12: 새로운 문자열 인스턴스가 생성됩니다 ( #2인수로)
  • 35: ac일반 객체로 비교if_acmpne

상수 문자열의 표현은 바이트 코드에서 매우 마술입니다.

  • 그것은 전용 갖는다 CONSTANT_String_info의 일반 오브젝트와 달리 구조 (예를 new String)
  • 구조체 는 데이터를 포함 하는 CONSTANT_Utf8_info 구조체를 가리 킵니다 . 이것이 문자열을 나타내는 데 필요한 유일한 데이터입니다.

위의 JVMS 인용문은 Utf8이 가리키는 경우마다 동일한 인스턴스가에 의해로드된다고 말합니다 ldc.

필드에 대해 비슷한 테스트를 수행했습니다.

  • static final String s = "abc"ConstantValue 속성을 통해 상수 테이블을 가리킴
  • 최종이 아닌 필드에는 해당 속성이 없지만 다음으로 초기화 할 수 있습니다. ldc

결론 : 문자열 풀에 대한 직접적인 바이트 코드 지원이 있으며 메모리 표현이 효율적입니다.

보너스 : 직접 바이트 코드를 지원하지 않는 Integer pool 과 비교하십시오 (예 : CONSTANT_String_info아날로그 없음 ).


19

자바 8 플러스 업데이트 . Java 8에서는 PermGen (영구 생성) 공간이 제거되고 메타 공간으로 대체됩니다. 문자열 풀 메모리는 JVM의 힙으로 이동됩니다.

Java 7에 비해 문자열 풀 크기가 힙에서 증가합니다. 따라서 내부화 된 문자열을위한 더 많은 공간이 있지만 전체 응용 프로그램을위한 메모리는 더 적습니다.

한 가지 더, Java에서 2 개의 객체를 ==참조 할 때 ' equals'는 객체의 참조 를 비교하는 데 사용되고 ' '는 객체의 내용을 비교하는 데 사용 된다는 것을 이미 알고 있습니다.

이 코드를 확인하자 :

String value1 = "70";
String value2 = "70";
String value3 = new Integer(70).toString();

결과:

value1 == value2 ---> 참

value1 == value3 ---> 거짓

value1.equals(value3) ---> 참

value1 == value3.intern() ---> 참

따라서 ' equals'를 사용 하여 2 개의 String 객체를 비교 해야 합니다. 그리고 그것이 intern()유용한 방법 입니다.


2

문자열 인터 닝은 컴파일러의 최적화 기술입니다. 하나의 컴파일 단위에 두 개의 동일한 문자열 리터럴이있는 경우 생성 된 코드는 어셈블리 내에서 해당 리터럴의 모든 인스턴스 (큰 따옴표로 묶은 문자)에 대해 하나의 문자열 오브젝트 만 작성되도록합니다.

나는 C # 배경에서 왔으므로 그 예를 제공하여 설명 할 수 있습니다.

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;

다음 비교 결과

Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true    
Console.WriteLine(obj == str2); // false !?

비고 1 : 객체는 참조로 비교됩니다.

참고 2 : : typeof (int) .Name은 리플렉션 방법으로 평가되므로 컴파일 타임에 평가되지 않습니다. 여기서 이러한 비교는 컴파일 타임에 이루어집니다.

결과 분석 : 1) 둘 다 동일한 리터럴을 포함하므로 생성 된 코드에 "Int32"를 참조하는 객체가 하나만 있기 때문에 true입니다. 참고 1을 참조하십시오 .

2) 두 값의 내용이 모두 동일하기 때문에 참입니다.

str2와 obj가 동일한 리터럴을 갖지 않기 때문에 3) FALSE. 참고 2를 참조 하십시오 .


3
그것보다 강하다. 동일한 클래스 로더에 의해로드 된 모든 문자열 리터럴은 동일한 문자열을 참조합니다. JLS 및 JVM 스펙을 참조하십시오.
Lorne의 후작

1
사실 @ user207421은 문자열 리터럴이 속한 클래스 로더와 관련이 없습니다.
Holger

1
Java interning() method basically makes sure that if String object is present in SCP, If yes then it returns that object and if not then creates that objects in SCP and return its references

for eg: String s1=new String("abc");
        String s2="abc";
        String s3="abc";

s1==s2// false, because 1 object of s1 is stored in heap and other in scp(but this objects doesn't have explicit reference) and s2 in scp
s2==s3// true

now if we do intern on s1
s1=s1.intern() 

//JVM checks if there is any string in the pool with value “abc” is present? Since there is a string object in the pool with value “abc”, its reference is returned.
Notice that we are calling s1 = s1.intern(), so the s1 is now referring to the string pool object having value abc”.
At this point, all the three string objects are referring to the same object in the string pool. Hence s1==s2 is returning true now.

0

OCP Java SE 11 프로그래머 Deshmukh 책에서 다음과 같은 Interning에 대한 가장 쉬운 설명을 찾았습니다. 문자열은 객체이므로 Java의 모든 객체는 항상 힙 공간에만 저장되므로 모든 문자열은 힙 공간에 저장됩니다. 그러나 Java는 힙 공간의 특수 영역 ( "문자열 풀")에서 새 키워드를 사용하지 않고 작성된 문자열을 유지합니다. Java는 새 키워드를 사용하여 작성된 문자열을 일반 힙 공간에 유지합니다.

문자열 풀의 목적은 고유 한 문자열 집합을 유지 관리하는 것입니다. new 키워드를 사용하지 않고 새 문자열을 만들 때마다 Java는 동일한 문자열이 문자열 풀에 이미 있는지 확인합니다. 존재하는 경우 Java는 동일한 String 오브젝트에 대한 참조를 리턴하고 그렇지 않은 경우 Java는 문자열 풀에 새 String 오브젝트를 작성하고 해당 참조를 리턴합니다. 예를 들어, 아래와 같이 코드에서 "hello"문자열을 두 번 사용하면 동일한 문자열에 대한 참조를 얻게됩니다. 다음 코드와 같이 == 연산자를 사용하여 서로 다른 두 개의 참조 변수를 비교하여이 이론을 실제로 테스트 할 수 있습니다 .

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); //prints true

String str3 = new String("hello");
String str4 = new String("hello");

System.out.println(str1 == str3); //prints false
System.out.println(str3 == str4); //prints false 

== 연산자는 단순히 두 개의 참조가 동일한 객체를 가리키는 지 여부를 확인하고 일치하는 경우 true를 반환합니다. 위의 코드에서 str2 는 앞에서 만든 것과 동일한 String 객체에 대한 참조를 가져옵니다. 그러나 str3str4 는 완전히 다른 두 개의 String 객체에 대한 참조를 얻습니다. 그렇기 때문에 str1 == str2 는 true를 반환하지만 str1 == str3str3 == str4 는 false를 반환합니다. 사실, 당신이 new String ( "hello"); 문자열 "hello"가 프로그램의 어느 곳에서나 처음으로 사용되는 경우 (하나는 인용 된 문자열 사용으로 인해 문자열 풀에, 하나는 일반 힙 공간에있는 경우) 하나가 아니라 두 개의 String 오브젝트가 작성됩니다. 새 키워드 사용

문자열 풀링은 동일한 값을 포함하는 여러 문자열 객체를 만들지 않도록하여 Java에서 프로그램 메모리를 절약하는 방법입니다. String의 인턴 메소드를 사용하여 새 키워드를 사용하여 작성된 문자열에 대한 문자열을 문자열 풀에서 가져올 수 있습니다. 이것을 문자열 객체의 "인터 닝"이라고합니다. 예를 들어

String str1 = "hello";
String str2 = new String("hello");
String str3 = str2.intern(); //get an interned string obj

System.out.println(str1 == str2); //prints false
System.out.println(str1 == str3); //prints true
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.