[java] Java의 문자열 상수 풀은 어디에 있습니까, 힙 또는 스택입니까?

저는 상수 풀의 개념과 JVM이 문자열 리터럴을 처리하는 데 사용하는 문자열 상수 풀을 알고 있습니다. 하지만 JVM에서 String 상수 리터럴을 저장하는 데 어떤 유형의 메모리를 사용하는지 모르겠습니다. 스택 또는 힙? 인스턴스와 연결되지 않은 리터럴이므로 스택에 저장 될 것이라고 가정합니다. 그러나 인스턴스에서 참조되지 않는 경우 리터럴은 GC 실행에 의해 수집되어야합니다 (내가 틀린 경우 수정). 스택에 저장되면 어떻게 처리됩니까?



답변

대답은 기술적으로 둘 다 아닙니다. Java Virtual Machine 사양에 따르면 문자열 리터럴을 저장하는 영역은 런타임 상수 풀에 있습니다 . 런타임 상수 풀 메모리 영역은 클래스 별 또는 인터페이스별로 할당되므로 개체 인스턴스에 전혀 연결되지 않습니다. 런타임 상수 풀은 “런타임 상수 풀, 필드 및 메서드 데이터와 같은 클래스 별 구조, 클래스 및 인스턴스 초기화 및 인터페이스에 사용되는 특수 메서드를 포함하여 메서드 및 생성자에 대한 코드를 저장 하는 메서드 영역 의 하위 집합입니다. 유형 초기화 “. VM 사양에 따르면 방법 영역은 논리적으로 힙의 일부이며, 메서드 영역에 할당 된 메모리가 힙에 할당 된 일반 데이터 구조와 관련된 가비지 수집 또는 기타 동작의 영향을 받음을 지시하지 않습니다.


답변

이 답변 에서 설명했듯이 문자열 풀의 정확한 위치는 지정되지 않았으며 JVM 구현마다 다를 수 있습니다.

Java 7까지 풀은 핫스팟 JVM에서 힙의 permgen 공간에 있었지만 Java 7 이후로 힙의 주요 부분으로 이동되었습니다 .

영역 : HotSpot
개요 : JDK 7에서 인턴 된 문자열은 더 이상 Java 힙의 영구 생성에 할당되지 않고 대신 Java 힙 의 주요 부분 (젊은 세대 및 구세대라고 함)에 할당됩니다. 응용 프로그램에서 만든 개체. 이 변경으로 인해 기본 Java 힙에 더 많은 데이터가 있고 영구 생성에 더 적은 데이터가 있으므로 힙 크기를 조정해야 할 수 있습니다. 대부분의 응용 프로그램은 이러한 변경으로 인해 힙 사용량에서 상대적으로 작은 차이 만 볼 수 있지만 많은 클래스를로드하거나 String.intern () 메서드를 많이 사용하는 대규모 응용 프로그램에서는 더 큰 차이를 볼 수 있습니다. RFE : 6962931

그리고 Java 8 Hotspot에서는 영구 생성이 완전히 제거되었습니다.


답변

문자열 리터럴은 스택에 저장되지 않습니다. 못. 실제로 스택에는 객체가 저장되지 않습니다.

문자열 리터럴 (또는 더 정확하게 이 를 나타내는 String 개체) 역사적으로 “permgen”힙이라는 힙에 저장되었습니다. (Permgen은 영구 생성의 약자입니다.)

정상적인 상황에서 문자열 리터럴과 permgen 힙의 다른 많은 항목은 “영구적으로”도달 할 수 있으며 가비지 수집되지 않습니다. (예를 들어, 문자열 리터럴은 항상이를 사용하는 코드 객체에서 도달 할 수 있습니다.) 그러나 더 이상 필요하지 않은 동적으로로드 된 클래스를 찾고 수집하도록 JVM을 구성 할 수 있으며 이로 인해 문자열 리터럴이 가비지 수집 될 수 있습니다. .

설명 # 1 -Permgen이 GC를받지 않는다는 것은 아닙니다. 일반적으로 JVM이 Full GC를 실행하기로 결정할 때 수행됩니다. 내 요점은 문자열 리터럴 을 사용하는 코드에 도달 할 수있는 한 도달 할 수 있고 코드의 클래스 로더에 도달 할 수있는 한 코드에 도달 할 수 있고 기본 클래스 로더의 경우 “영원히”를 의미한다는 것입니다.

CLARIFICATION # 2- 실제로 Java 7 이상은 일반 힙을 사용하여 문자열 풀을 보유합니다. 따라서 String 리터럴과 인턴 문자열을 나타내는 String 개체는 실제로 일반 힙에 있습니다. (자세한 내용은 @assylias의 답변을 참조하십시오.)


하지만 여전히 문자열 리터럴 저장과 .NET으로 만든 문자열 사이의 얇은 선을 찾으려고 노력하고 new있습니다.

“가는 선”이 없습니다. 정말 간단합니다.

  • String 문자열 리터럴을 나타내거나 해당하는 개체는 문자열 풀에 보관됩니다.
  • StringString::intern호출에 의해 생성 된 개체 는 문자열 풀에 보관됩니다.
  • 다른 모든 String개체는 문자열 풀에 보관되지 않습니다.

그런 다음 문자열 풀이 “저장”되는 위치에 대한 별도의 질문이 있습니다. Java 7 이전에는 permgen 힙이었습니다. Java 7부터는 기본 힙입니다.


답변

문자열 풀링

문자열 풀링 (때때로 문자열 정규화라고도 함)은 값이 같지만 ID가 다른 여러 String 개체를 단일 공유 String 개체로 바꾸는 프로세스입니다. 사용자 고유의 맵 (요구 사항에 따라 소프트 또는 약한 참조 포함)을 유지하고 맵 값을 정규화 된 값으로 사용하여이 목표를 달성 할 수 있습니다. 또는 JDK에서 제공하는 String.intern () 메서드를 사용할 수 있습니다.

Java 6에서 String.intern ()을 사용하는 경우 풀링이 제어를 벗어난 경우 OutOfMemoryException이 발생할 가능성이 높기 때문에 많은 표준에서 금지되었습니다. 문자열 풀링의 Oracle Java 7 구현이 크게 변경되었습니다. http://bugs.sun.com/view_bug.do?bug_id=6962931
http://bugs.sun.com/view_bug.do?bug_id=6962930 에서 자세한 내용을 찾을 수 있습니다
.

Java 6의 String.intern ()

옛날에는 인턴 된 모든 문자열이 PermGen에 저장되었습니다.이 부분은 주로로드 된 클래스와 문자열 풀을 저장하는 데 사용되는 힙의 고정 크기 부분입니다. 명시 적으로 인턴 된 문자열 외에도 PermGen 문자열 풀에는 프로그램에서 이전에 사용 된 모든 리터럴 문자열이 포함되어 있습니다 (여기서 중요한 단어가 사용됩니다. 클래스 또는 메서드가로드 / 호출되지 않은 경우 그 안에 정의 된 상수는로드되지 않습니다).

Java 6에서 이러한 문자열 풀의 가장 큰 문제는 위치 인 PermGen입니다. PermGen은 크기가 고정되어 있으며 런타임에 확장 할 수 없습니다. -XX : MaxPermSize = 96m 옵션을 사용하여 설정할 수 있습니다. 내가 아는 한 기본 PermGen 크기는 플랫폼에 따라 32M에서 96M까지 다양합니다. 크기를 늘릴 수 있지만 크기는 고정됩니다. 이러한 제한에는 String.intern을 매우 신중하게 사용해야합니다.이 방법을 사용하여 제어되지 않은 사용자 입력을 인턴하지 않는 것이 좋습니다. 이것이 자바 6 시대의 문자열 풀링이 대부분 수동 관리 맵에서 구현 된 이유입니다.

Java 7의 String.intern ()

Oracle 엔지니어는 Java 7의 문자열 풀링 논리에 매우 중요한 변경을 수행했습니다. 문자열 풀은 힙으로 재배치되었습니다. 이는 더 이상 별도의 고정 크기 메모리 영역에 의해 제한되지 않음을 의미합니다. 모든 문자열은 이제 대부분의 다른 일반 개체와 같이 힙에 위치하므로 애플리케이션을 조정하는 동안 힙 크기 만 관리 할 수 ​​있습니다. 기술적으로 이것만으로도 Java 7 프로그램에서 String.intern () 사용을 재고 할 충분한 이유가 될 수 있습니다. 그러나 다른 이유가 있습니다.

문자열 풀 값은 가비지 수집됩니다.

예, JVM 문자열 풀의 모든 문자열은 프로그램 루트에서 참조가없는 경우 가비지 수집에 적합합니다. 논의 된 모든 Java 버전에 적용됩니다. 인턴 된 문자열이 범위를 벗어 났고 이에 대한 다른 참조가없는 경우 JVM 문자열 풀에서 가비지 수집됩니다.

가비지 수집에 적합하고 힙에 상주하는 JVM 문자열 풀은 모든 문자열에 적합한 장소 인 것 같습니다. 이론적으로는 사실입니다. 사용되지 않은 문자열은 풀에서 가비지 수집되고 사용 된 문자열은 입력에서 동일한 문자열을 얻을 경우 메모리를 절약 할 수 있습니다. 완벽한 메모리 절약 전략으로 보입니까? 거의 그렇습니다. 결정을 내리기 전에 문자열 풀이 어떻게 구현되는지 알아야합니다.

출처.


답변

다른 답변에서 설명했듯이 Java의 메모리는 두 부분으로 나뉩니다.

1. 스택 : 스레드 당 하나의 스택이 생성되고 스택 프레임을 저장하여 다시 로컬 변수를 저장하고 변수가 참조 유형이면 해당 변수는 실제 개체에 대한 힙의 메모리 위치를 참조합니다.

2. 힙 : 모든 종류의 개체가 힙에만 생성됩니다.

힙 메모리는 다시 세 부분으로 나뉩니다.

1. Young Generation : 수명이 짧은 물건을 저장하고 Young Generation 자체는 Eden SpaceSurvivor Space 로 나눌 수 있습니다 .

2. 구세대 : 많은 가비지 수집주기에서 살아남아 여전히 참조되고있는 객체를 저장합니다.

3. 영구 생성 : 프로그램에 대한 메타 데이터 (예 : 런타임 상수 풀)를 저장합니다.

문자열 상수 풀은 힙 메모리의 영구 생성 영역에 속합니다.

javap -verbose class_name메서드 참조 (#Methodref), 클래스 객체 (#Class), 문자열 리터럴 (#String)을 표시하는 을 사용하여 바이트 코드에서 코드의 런타임 상수 풀을 볼 수 있습니다.

런타임 상수 풀

내 기사 에서 JVM이 메소드 오버로딩 및 오버 라이딩을 내부적으로 처리하는 방법 에서 더 많은 것을 읽을 수 있습니다 .


답변

이미 여기에 포함 된 훌륭한 답변에 제 관점에서 누락 된 부분 인 일러스트레이션을 추가하고 싶습니다.

이미 JVM은 Java 프로그램에 할당 된 메모리를 두 부분으로 나눕니다. 하나는 스택 이고 다른 하나는 입니다. 스택은 실행 목적으로 사용되며 힙은 저장 목적으로 사용됩니다. 해당 힙 메모리에서 JVM은 특별히 문자열 리터럴을위한 일부 메모리를 할당합니다. 힙 메모리의이 부분을 문자열 상수 풀이 라고 합니다 .

예를 들어 다음 객체를 초기화하면 :

String s1 = "abc";
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);

문자열 리터럴 s1s2문자열 상수 풀에 갈 것이다는 obj2보다 힙에 OBJ3으로 obj1 객체. 이들 모두는 스택에서 참조됩니다.

또한 “abc”는 힙 및 문자열 상수 풀에 나타납니다. 왜 String s1 = "abc"String obj1 = new String("abc")이 방법으로 생성됩니다? 이는 String 객체 String obj1 = new String("abc")의 새롭고 참조 적으로 구별되는 인스턴스를 명시 적으로 만들고 String s1 = "abc"사용 가능한 경우 문자열 상수 풀의 인스턴스를 재사용 할 수 있기 때문 입니다. 더 자세한 설명 : https://stackoverflow.com/a/3298542/2811258

여기에 이미지 설명 입력


답변