program story

문자열 리터럴 풀은 문자열 개체에 대한 참조 모음이거나 개체 모음입니다.

inputbox 2020. 11. 5. 08:00
반응형

문자열 리터럴 풀은 문자열 개체에 대한 참조 모음이거나 개체 모음입니다.


SCJP Tip Line의 저자 Corey McGlone의 javaranch 사이트에 대한 기사를 읽은 후 저는 모두 혼란 스럽습니다. Strings, Literally 및 Kathy Sierra (javaranch의 공동 창립자)와 Bert Bates의 SCJP Java 6 프로그래머 가이드.

Corey 씨와 Kathy Sierra 씨가 String Literal Pool에 대해 인용 한 내용을 인용하려고합니다.

1. Corey McGlone에 따르면 :

  • 문자열 리터럴 풀은 문자열 개체를 가리키는 참조 모음입니다.

  • String s = "Hello";( "Hello"라는 힙에 개체가 없다고 가정), "Hello"힙에 문자열 개체 만들고 문자열 리터럴 풀 (상수 테이블)에이 개체에 대한 참조를 배치합니다.

  • String a = new String("Bye");( "Bye"라는 이름의 힙에 개체가 없다고 가정하면 new운영자는 JVM이 힙에 개체를 만들도록 요구합니다.

이제 "new"문자열 생성을위한 연산자와 그 참조에 대한 설명이이 기사에서 약간 혼란 스럽기 때문에 아래에있는대로 기사 자체의 코드와 설명을 넣습니다.

public class ImmutableStrings
{
    public static void main(String[] args)
    {
        String one = "someString";
        String two = new String("someString");

        System.out.println(one.equals(two));
        System.out.println(one == two);
    }
}

이 경우 실제로 키워드로 인해 약간 다른 동작이 발생합니다.이 경우 "new."두 문자열 리터럴에 대한 참조는 여전히 상수 테이블 (문자열 리터럴 풀)에 저장되지만 키워드에 도달하면 "new,"JVM은 상수 테이블의 객체를 사용하는 대신 런타임에 새 String 객체를 만들어야합니다.

다음은이를 설명하는 다이어그램입니다 ..

여기에 이미지 설명 입력

그렇다면 String Literal Pool도이 Object에 대한 참조를 가지고 있다는 뜻입니까?

Corey McGlone의 기사 링크입니다.

http://www.javaranch.com/journal/200409/Journal200409.jsp#a1

2. SCJP 책의 Kathy Sierra와 Bert Bates에 따르면 :

  • Java의 메모리 효율성을 높이기 위해 JVM은 "문자열 상수 풀"이라는 특수한 메모리 영역을 따로 설정하고 컴파일러가 문자열 리터럴을 만나면 풀에서 동일한 문자열이 이미 있는지 여부를 확인합니다. 그렇지 않은 경우 새 문자열 리터럴 개체를 만듭니다.

  • String s = "abc"; // 하나의 String 객체와 하나의 참조 변수를 만듭니다 ....

    괜찮습니다.하지만 다음 문장에 혼란 스러웠습니다.

  • String s = new String("abc") // 두 개의 개체와 하나의 참조 변수를 만듭니다.

    그것은 책에서 말하고있다 .... 일반 (풀이 아닌) 메모리에있는 새로운 String 객체, 그리고 "s"는 그것을 참조 할 것이다. 반면 추가 리터럴 "abc"는 풀에 배치 될 것이다.

    책에서 위의 줄은 Corey McGlone의 기사에있는 줄과 충돌합니다.

    • Corey McGlone이 언급 한 것처럼 String Literal Pool이 String 객체에 대한 참조 모음 인 경우 리터럴 객체 "abc"를 풀에 배치해야하는 이유는 무엇입니까 (책에서 언급했듯이)?

    • 그리고이 문자열 리터럴 풀은 어디에 있습니까?

코드를 작성하는 동안에는 그다지 중요하지 않지만 메모리 관리 측면에서 매우 중요하므로이 기본 사항을 지우고 싶은 이유입니다.


여기서 이해해야 할 요점은 StringJava 객체와 그 내용 의 차이 -private fieldchar[] 아래에 있다고 생각합니다 . 기본적으로 배열을 감싸는 래퍼로 캡슐화하고 수정할 수 없게하여 변경 불가능한 상태로 유지할 수 있습니다. 또한 클래스는이 배열의 어떤 부분이 실제로 사용되었는지 기억합니다 (아래 참조). 이것은 모두 동일한을 가리키는 두 개의 다른 객체 (매우 가벼운)를 가질 수 있음을 의미합니다 .valueStringchar[]StringStringStringchar[]

hashCode()각각 의 내부 필드 String함께 몇 가지 예를 보여 드리겠습니다 ( 문자열과 구별하기 위해 텍스트 라고 부를 것입니다 ). 마지막으로 테스트 클래스의 상수 풀과 함께 출력을 보여 드리겠습니다 . 클래스 상수 풀과 문자열 리터럴 풀을 혼동하지 마십시오. 그들은 완전히 동일하지 않습니다. 상수 풀에 대한 javap의 출력 이해를 참조하십시오 .hashCode()char[] valuejavap -c -verbose

전제 조건

테스트 목적으로 String캡슐화 를 중단하는 유틸리티 메서드를 만들었습니다 .

private int showInternalCharArrayHashCode(String s) {
    final Field value = String.class.getDeclaredField("value");
    value.setAccessible(true);
    return value.get(s).hashCode();
}

그것은 인쇄됩니다 hashCode()char[] value효율적 우리가 여부를이 특정 이해할 수 있도록, String같은 포인트 char[]텍스트 또는 아닙니다.

클래스의 두 문자열 리터럴

가장 간단한 예부터 시작하겠습니다.

자바 코드

String one = "abc";
String two = "abc";

BTW 단순히 작성 "ab" + "c"하면 Java 컴파일러가 컴파일 시간에 연결을 수행하고 생성 된 코드는 정확히 동일합니다. 이것은 컴파일 시간에 모든 문자열이 알려진 경우에만 작동합니다.

클래스 상수 풀

각 클래스에는 자체 상수 풀 ( 소스 코드에서 여러 번 발생하는 경우 재사용 할 수있는 상수 값 목록)이 있습니다. 여기에는 일반적인 문자열, 숫자, 메서드 이름 등이 포함됩니다.

위의 예에서 상수 풀의 내용은 다음과 같습니다.

const #2 = String   #38;    //  abc
//...
const #38 = Asciz   abc;

주목해야 할 중요한 점 은 문자열이 가리키는 String상수 객체 ( #2)와 유니 코드 인코딩 텍스트 "abc"( #38)의 차이입니다.

바이트 코드

다음은 생성 된 바이트 코드입니다. onetwo참조는 모두 문자열을 #2가리키는 동일한 상수 로 할당됩니다 "abc".

ldc #2; //String abc
astore_1    //one
ldc #2; //String abc
astore_2    //two

산출

각 예에 대해 다음 값을 인쇄하고 있습니다.

System.out.println(showInternalCharArrayHashCode(one));
System.out.println(showInternalCharArrayHashCode(two));
System.out.println(System.identityHashCode(one));
System.out.println(System.identityHashCode(two));

두 쌍이 동일하다는 것은 놀라운 일이 아닙니다.

23583040
23583040
8918249
8918249

즉, 두 개체가 동일한 char[](아래의 동일한 텍스트)을 가리킬뿐만 아니라 equals()테스트가 통과됩니다. 그러나 더 많은, one그리고 two동일한 참조입니다! 그래서 one == two뿐만 아니라 사실이다. 분명히 경우 onetwo다음 같은 개체를 가리킨 one.valuetwo.value같아야합니다.

리터럴 및 new String()

자바 코드

이제 우리 모두가 기다린 예는 String동일한 리터럴을 사용하는 하나의 문자열 리터럴과 하나의 새로운 문자열 입니다. 어떻게 작동할까요?

String one = "abc";
String two = new String("abc");

The fact that "abc" constant is used two times in the source code should give you some hint...

Class constant pool

Same as above.

Byte code

ldc #2; //String abc
astore_1    //one

new #3; //class java/lang/String
dup
ldc #2; //String abc
invokespecial   #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V
astore_2    //two

Look carefully! The first object is created the same way as above, no surprise. It just takes a constant reference to already created String (#2) from the constant pool. However the second object is created via normal constructor call. But! The first String is passed as an argument. This can be decompiled to:

String two = new String(one);

Output

The output is a bit surprising. The second pair, representing references to String object is understandable - we created two String objects - one was created for us in the constant pool and the second one was created manually for two. But why, on earth the first pair suggests that both String objects point to the same char[] value array?!

41771
41771
8388097
16585653

It becomes clear when you look at how String(String) constructor works (greatly simplified here):

public String(String original) {
    this.offset = original.offset;
    this.count = original.count;
    this.value = original.value;
}

See? When you are creating new String object based on existing one, it reuses char[] value. Strings are immutable, there is no need to copy data structure that is known to be never modified.

I think this is the clue of your problem: even if you have two String objects, they might still point to the same contents. And as you can see the String object itself is quite small.

Runtime modification and intern()

Java code

Let's say you initially used two different strings but after some modifications they are all the same:

String one = "abc";
String two = "?abc".substring(1);  //also two = "abc"

The Java compiler (at least mine) is not clever enough to perform such operation at compile time, have a look:

Class constant pool

Suddenly we ended up with two constant strings pointing to two different constant texts:

const #2 = String   #44;    //  abc
const #3 = String   #45;    //  ?abc
const #44 = Asciz   abc;
const #45 = Asciz   ?abc;

Byte code

ldc #2; //String abc
astore_1    //one

ldc #3; //String ?abc
iconst_1
invokevirtual   #4; //Method String.substring:(I)Ljava/lang/String;
astore_2    //two

The fist string is constructed as usual. The second is created by first loading the constant "?abc" string and then calling substring(1) on it.

Output

No surprise here - we have two different strings, pointing to two different char[] texts in memory:

27379847
7615385
8388097
16585653

Well, the texts aren't really different, equals() method will still yield true. We have two unnecessary copies of the same text.

Now we should run two exercises. First, try running:

two = two.intern();

before printing hash codes. Not only both one and two point to the same text, but they are the same reference!

11108810
11108810
15184449
15184449

This means both one.equals(two) and one == two tests will pass. Also we saved some memory because "abc" text appears only once in memory (the second copy will be garbage collected).

The second exercise is slightly different, check out this:

String one = "abc";
String two = "abc".substring(1);

Obviously one and two are two different objects, pointing to two different texts. But how come the output suggests that they both point to the same char[] array?!?

23583040
23583040
11108810
8918249

나는 당신에게 대답을 남길 것입니다. 어떻게 substring()작동하는지, 그러한 접근 방식의 장점은 무엇이며, 언제 큰 문제를 일으킬 수 있는지 알려줍니다 .

참고 URL : https://stackoverflow.com/questions/11700320/is-string-literal-pool-a-collection-of-references-to-the-string-object-or-a-col

반응형