본문 바로가기

Java

String과 String Constant Pool

들어가기 앞서

 

String을 보다 자세히 이해하기 위해서는 JVM 구조를 알고 있는 것이 좋다. JVM에 대한 자세한 내용을 알고 싶다면 https://jaystorage.tistory.com/8 을 참고하자

 

 


String 이란

 

Java에서 String은 문자열을 나타내는 객체이다. C/C++에서는 문자열이 단순히 char의 배열인데 반해, Java에서의 문자열은 java.lang안에 있는 String 클래스의 객체를 의미한다.

 

그렇기 때문에 String 은 int 와 char 와 같은 기본형(primitive type)이 아닌 참조형(reference type) 변수로 분류된다. (String의 첫 글자가 소문자가 아닌 대문자로 시작하는 이유가 이 때문이다.)

 


 

Java의 String 은 불변(immutable)하다

 

Java에서는 String의 값을 변경할 수 없다.

String str1 = "aaa";
str1 = str1 + "bbb";

System.out.println(str1); // aaabbb

 

일반적으로 int 변수를 선언한 뒤 그 값을 바꾸면 기존 값을 변경된 값으로 덮어쓴다. 위 코드에서 보면 str1이라는 문자열도 동일한 방식으로 변경되는 것처럼 보이지만 실제로는 새로운 String 객체가 생성되는 것이다. 이러한 String 객체가 불변으로 설계된 이유는 다음과 같다.

 

  1. 캐싱
    - Java에서 String은 String Constant Pool이라는 영역에 따로 저장되고 여러 객체 또는 변수에서 공유하여 사용할 수 있다.
    - 계속해서 같은 String을 만들지 않고 이미 만들어진 String (캐싱된 String)을 사용하면 그만큼 성능적 이득을 취할 수 있다.

  2. 동기화
    - 불변한 객체는 멀티 스레드(multi-thread)환경에서 동기화 문제가 발생하지 않기 때문에 안전하다.

  3. 보안
    - 대부분의 프로그램에서 DB 커넥션 등 여러 상황에서 계정정보(아이디, 비밀번호)를 통해 인증을 수행한다. 그리고 이 계정정보는 주로 String으로 이루어져있다.
    - String이 mutable(변화가능)하다면 해커의 공격으로 String값이 바뀔 수 있는 여지를 줄 수 있다.

 

 


 

String의 생성방식과 String이 저장되는 공간

 

Java에서 String을 생성하는 방법은 다음과 같이 리터럴을 이용한 방식과 new 연산자를 통한 방식이 있다.

String str1 = "Hello"; // 문자열 리터럴을 이용한 방식
String str2 = "Hello";

String str3 = new String("Hello"); // new 연산자를 이용한 방식
String str4 = new String("Hello");

 

그리고 위의 String을 서로 비교한 결과는 다음과 같다.

// 리터럴 문자열 비교
System.out.println(str1 == str2); // true

// 객체 문자열 비교
System.out.println(str3 == str4); // false
System.out.println(str3.equals(str4)); // true

// 리터럴과 객체 문자열 비교
System.out.println(str1 == str3); // false
System.out.println(str3.equals(str1)); // true

 

그런데 결과를 보면 조금 이상한 점이 있다. 분명 String은 primitive type이 아니기 때문에 == 을 사용해서 값을 비교하는 것이 불가능 할 것 같은데 리터럴로 선언된 str1과 str2를 비교한 결과는 true를 반환하고 있다.

이러한 결과가 나오는 이유는 String이 저장될 때 JVM의 특별한 곳에 저장되기 때문이다.

 


 

String Constant Pool

 

위에서 살펴봤듯이 String은 선언하는 방식에 따라 메모리에 적재되는 방식이 다르다.

위 예제의 str3이나 str4처럼 new를 통해 String을 생성하면 이 값은 JVM의 Heap 영역에 존재하게 된다. 반면 str1이나 str2처럼 리터럴을 사용해 String을 생성하면 이 값은 Heap 영역 내부에 있는 String Constant Pool이라는 영역에 저장된다.

위 코드를 그림으로 표현하면 다음과 같다.

 

이 그림을 보면 str1 == str2 의 결과가 왜 true인지 알 수 있다. 리터럴 값으로 할당한 두 변수 str1, str2 가 같은 메모리 주소를 가리키고 있기 때문이다.

이러한 String Constant Pool이 Heap 영역 내에서 따로 존재하는 이유는 String이 그만큼 많이 사용되기 때문이다. 프로그램 실행 중 동일한 String이 여러 번 사용될 때 String Constant Pool에 단 하나의 String 인스턴스만 생성한 뒤 공유하게 만들면 메모리 사용을 최소화 할 수 있다.

 


 

Java 버전 별 String Constant Pool

 

String Constant Pool이 메모리상에서 차지하는 영역은 Java의 버전에 따라 다르다.

 

  • Java 6
    String Constant Pool이 Heap 영역대신 PermGen(Permenent Generation) 영역에 존재했다. PermGen은 JVM에서 메소드 영역에 있기 때문에 GC가 일어나지 않았고, 사이즈가 32MB~96MB로 고정되어 있었다. 따라서 당연하게도 너무 다양한 String을 사용하게 된다면 OOM이 발생하고는 했다.

  • Java 7
    이전 버전에서 가지고 있던 여러가지 이유 때문에 String Constant Pool은 Java 7부터 PermGen 영역에서 Heap 영역으로 위치를 옮겼다. Heap 사이즈에 따라 String Pool도 영향을 받기에 동적으로 메모리크기가 할당될 수 있게 되었다. PermGen 영역은 여전히 고정 메모리크기로 할당되었다.

  • Java 8
    이때부터 고정크기로 많은 문제를 일으킨 PermGen은 Metaspace로 이름을 변경하고 고정크기에서 동적크기로 메모리 구성이 변경되었다.


출처 : 
https://jiwondev.tistory.com/114
https://inpa.tistory.com/entry/JAVA-%E2%98%95-String-%ED%83%80%EC%9E%85-%ED%95%9C-%EB%88%88%EC%97%90-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-String-Pool-%EB%AC%B8%EC%9E%90%EC%97%B4-%EB%B9%84%EA%B5%90
https://pjh3749.tistory.com/255
https://velog.io/@ur2e/String%EC%9D%80-%EC%99%9C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%B6%88%EB%B3%80-%EA%B0%9D%EC%B2%B4%EC%9D%B8%EA%B0%80%EC%9A%94-String-Constant-Pool

 

'Java' 카테고리의 다른 글

String / StringBuilder / StringBuffer  (0) 2024.05.23
BigDecimal  (0) 2024.05.20
상수(Constant)와 리터럴(Literal)  (0) 2024.03.17
GC(Garbage Collection, Garbage Collector)  (1) 2023.10.29
JVM 구조  (0) 2023.10.22