String pool (String Constant Pool) + String intern()
StringBuilder와 StringBuffer의 차이점에 대해 알기전에, Java가 String을 어떻게 관리하는지 먼저 알아보자.
Java에서는 String을 저장하는데 2곳
의 메모리를 사용할 수 있다.
- ConstantPool
- Heap
String의 생성 방법에 따라 다르게 두 영역에 다르게 저장된다.
String을 생성하기 위해서 2가지 방법을 이용할 수 있다.
- String Literal(리터럴)을 이용하는 방법
- new 연산자를 통해 String 객체의 생성자로 문자열 value 주입
String literal = "string!"; // 리터럴로 String 객체 생성
String newStr = new String("string!") // new 연산자로 String 객체 생성
생성자인 new 연산자를 이용한 문자열 생성 방식과, 문자열 리터럴 생성 방식은
겉으로 보았을 때의 문법 차이도 있지만 실제 메모리에 할당되는 영역에도 차이가 있다.
문자열(String)은 레퍼런스 타입이지만 리터럴을 지원한다. 리터럴 방식으로 String 에 값을 주면 Heap 영역에서 String constant pool 이라는 특수한 영역에 값이 저장된다. 동일한 값을 쓰는 경우에 다른 일반적인 레퍼런스타입 처럼 Heap영역에 다시 할당되지 않고, String constant pool 에 존재하는 값을 참조하는 방식으로 작동하게되는 이점이 있다.
String 리터럴 생성 방식을 이용한 경우에는 String Constant Pool
이라는 영역에 할당되며,
new 연산자를 이용한 String 객체를 생성한 경우에는 메모리의 Heap
영역에 할당된다.
- 문자열이 담기는 상수풀의 위치는 자바7 부터 Heap 영역으로 옮겨졌다.
즉, 문자열 리터널 생성 방식을 사용한 String과, new 키워드를 사용한 String은 메모리에 적재 장소가 달라진다.
리터럴을 사용한 String
String literal = "string!"; // 리터럴로 String 객체 생성
리터럴로 String을 생성하면 Constant Pool 이란 곳에 값이 할당된다.
String을 효율적으로 사용하기 위해 JVM상에서 String을 다른 객체들과 차별되게 저장되도록 한 공간이
바로 Heap Area 상의 String Constant Pool이다.
String Constant Pool 은 Heap 영역 내에 존재하는데, String의 재사용을 위해 사용된다.
String은 String 리터럴로 값을 생성하면 Constant Pool에 사용자가 정의한 value로 저장되어 재사용 된다.
String Constant Pool은 한 번 저장한 변수를 다시 저장하지 않도록 만들어졌다.
따라서 String을 " "(리터럴)을 이용하여 선언할 경우 먼저 String Constant Pool에 해당 변수가 있는지 확인 후,
있으면 기존 값을 참조하도록 주소값을 설정하고, 아니라면 새로운 String은 String Constant Pool에 넣는다.
new 연산자를 사용한 String
Java의 레퍼런스 타입(참조 타입) 변수들은, Heap 영역에 보관된다.
new 연산자를 사용하여 생성한 String은 Heap 영역에 각기 다른 장소에 보관되게 된다.
new 연산자로 Heap 영역에 생성된 String들은 각기 다른 주소값을 가지고 있으며,
String hello1 = new String("hello");
String hello2 = new String("hello");
위 코드와 같이 같은 문자열 값을 가지고 있어도 다른 주소값을 참조한다.
System.out.println(hello1 == hello2); //false
System.out.println(hello1.equals(hello2)); //true
primitive Type이 아닌 레퍼런스 타입에서의 ==
비교는 대상의 주소를 비교하는 연산자이다.
값이 hello
로 같지만, new 연산자를 통해 다른 주소에 생성되었으므로 둘은 다른 주소를 가지고 있다.
- Heap 영역에 생성된 String 객체와 리터럴을 이용해 string constant pool에 저장된 String 객체의 주소값은 다를 수밖에 없다.
- Heap 영역안에 있는 String pool 을 참조하는게 아닌, Heap 영역의 Constant Pool 바깥에 메모리 상태로 생성된다.
- 즉, new 연산자로 생성한 String 객체는 같은 값이 String Pool에 이미 존재하더라도, Heap 영역 내 별도의 객체를 가리키게 된다.
결론 : String은 리터럴로 생성하자
String 객체를 new 연산자로 생성하면, 같은 값이라 할지라도 Heap 영역에 매번 새로운 객체가 생성된다.
따라서 String이 갖는 불변성이라는 장점을 누리지 못한다.
결국 메모리를 효율적으로 사용하기 위해서는 항상 String 리터럴로 String을 생성하는 것이 좋다.
String 객체의 비교는 equals() 메소드를 사용해야 한다
String은 초기화 방법에 따라서 다른 비교 결과가 나올 수 있으므로
- 리터럴로 생성하면 == 비교로 값이 같은지 비교 가능하고
- new연산자로 비교하면, 값은 같아도 주소가 다르니 무조건 false이다.
String비교에 왜 '==' 연산자보다 equals() 메소드를 사용하는 것이 좋은 방법인가?
equals()메서드는 값을 비교하기 때문이다.
그러므로 String객체의 내용을 비교해 줄 수 있는 String의 equals()을 사용하는 것이 항상 옳다고 할 수 있다.
String객체의 수정을 자주 하지 말자
리터럴로 String 객체를 생성하고, 값을 수정하게 된다면
계속해서 수정된 새로운 리터럴이 String Constant Pool에 생성되므로 성능이 떨어진다.
String클래스 대신 StringBuffer또는 StringBuilder클래스를 사용해야 한다.
String Interning
String Constants Pool에 저장하고 사용하는 것을 String Interning 이라고 한다.
String 클래스에는 intern()이라는 메서드가 있다.
String Intern() 플로우
리터럴로 생성한 String은, String Pool을 이용하기 때문에 같은 값에 대해서는 새로운 메모리에 할당 없이 재사용 가능하다는 장점을 가지고 있다는 것을 알 수 있다.
- String의 값이 String Constants Pool에 있는지 찾는다
- 있다면 바로 반환하여 값을 참조한다.
- 없다면 StringConstants Pool에 새로운 값을 할당
- 반환하여 값을 참조한다.
즉, String Pool은 String Interning을 이용하여 동일한 값이라면 그 값을 참조할 수 있다
intern() 메서드는 해당 String과 동등한(equal; 값이 같음) String 객체가 이미 String Constant Pool에 존재하면 그 객체를 반환한다.
String Constant Pool에 존재하지 않는다면, 호출된 String 객체를 String Constant Pool에 추가하고 객체의 reference를 반환한다.
즉 new로 생성한 String 객체를 String contant Pool에 넣어 재사용 하려면 intern()메소드를 사용하면 된다.
'Java > Java' 카테고리의 다른 글
Java - 동일성과 동등성 ( ==, equals() ) (0) | 2022.11.19 |
---|---|
Java 박싱과 언박싱, 오토박싱 그리고 성능상 주의할점 (0) | 2022.11.13 |
Java 람다표현식의 특징과 Scope, effectively final variable (0) | 2022.10.23 |
Java String Builder와 StringBuffer의 차이점 (0) | 2022.10.21 |
# Java 그룹화, 그룹화 하고 정렬. Stream groupby, groupingBy, sorting Lists after groupingBy (0) | 2022.09.07 |