String은 두 가지 생성 방식이 있고 각각의 차이점이 존재한다.
두 가지 방식에는 큰 차이점이 있다. new를 통해 String을 생성하면 Heap 영역에 존재하게 되고 리터럴을 이용할 경우 string constant pool이라는 영역에 존재하게 된다.
다음의 예제 코드를 보자.
String literal = "loper";
String b = new String("loper");
System.out.println(literal == object);
System.out.println(literal.equals(object);
Java에서 == 연산은 객체의 주소값을 비교할 때 사용하고 값을 비교할 때는 equals() 함수를 쓴다. == 연산의 결과는 false이고 equals의 결과는 true이다. equals는 객체의 값을 비교하기 때문에 같은 문자열에 대해서 true가 나온다. 하지만 == 연산자의 경우 Heap 영역에 생성된 String 객체의 주소값을 비교하기 때문에 결과는 false가 나온다.
String을 literal로 선언한 경우 내부적으로 String의 intern() 메소드가 호출된다. intern() 메소드는 주어진 문자열이 string constant pool에 존재하는지 검색하고, 있다면 그 주소값을 반환하고 없다면 string constant pool에 넣고 새로운 주소값을 반환한다.
String literal = "loper";
String object = new String("loper");
String intern = object.intern();
System.out.println(literal == object); //false
System.out.println(literal.equlas(object); //true
System.out.println(literal == intern); //true
System.out.println(literal.equals(intern)); //true
기존에 new를 통해 생성된 String 객체와 리터럴로 생성된 String 객체를 == 연산하였을 경우 false를 반환하였지만 new를 통해 생성된 String 객체의 intern() 메소드를 호출하여 새로운 String 객체에 대입하였을 경우 리터럴로 생성된 String 객체와 == 연산시 true를 반환하게 된다.
리터럴로 "loper"라는 문자열이 String constant pool에 저장되어 있고 intern() 메소드를 호출하면서 string constant pool에 "loper"라는 문자열을 검색하게 되고 이미 저장된 "loper" 문자열이 있기 때문에 동일한 주소값을 반환해 true를 반환하는 것이다.
intern() 메소드가 호출이 되면,
intern은 Heap의 메모리를 아낄 수 있다는 장점이 있다.
하지만, intern() 메소드를 사용하는 단점도 있다.
Java6에서 string constant pool의 위치는 Perm 영역에 있었는데 Perm 영역은 고정된 사이즈이고 Runtime 시에 사이즈가 확장되지 않기 때문에 OutofMemoryException을 발생시킬 수 있고 메모리 관리가 불가능하기 때문에 intern() 메소드를 사용하는 것이 권장되지 않았다.
그러나 Java 7에서 string constant pool의 위치가 Perm 영역에서 Heap 영역으로 변경되었다.
따라서, string constant pool에 있는 문자열도 GC의 대상이 되기 때문에 효율적인 메모리 관리가 가능해 졌다.
참고