카페에 적었던글을 다시 옮겨 둡니다.
----
안녕하세요.
 찬 입니다.

오늘도 기초시리즈.
String의 intern()에 대해서 이야기 해 보도록 하죠.

intern() 에 대해서 알기 위해서는, 우선 String 자체에 대해서 좀 알아 봐야 합니다.

String str1 = "Hello";
String str2 = "Hello";
String str3 = "Hello";


이렇게 해 두면 str1과 str2와 str3는 모두 하나의 객체를 가리키고 있습니다.
왜 그런지 알아 봅시다.

.java파일을 컴파일 하게 되면, .class파일이 만들어 지게 됩니다.
.class 파일 안에는 현재 클래스의 정보가 들어있게 되겠지요.

complie할때에 이미 저 문자를 사용해야 한다는것을 알수 있기 때문에 .class파일안에다가 바로 문자열을 저장해 두는것입니다.
이때 "Hello"라는 문자열은 .class파일의 "String pool"에 들어가게 됩니다.
( 이 class파일이 실제로 로딩이 되면, "Hello"가 메모리의 "String pool"에 로딩됩니다. )

그런데, 총 3개의 "Hello"라는 문자열이 있는데, .class파일에 3개씩이나 넣어야 할까요?
모두 같은 문자열이니 이때는 "String pool"에 1개의 "Hello"만 들어가게 됩니다. 이게 가장 효율적이니깐요..
( 왜 그렇냐고 물으신다면, 원래 그렇게 만들어서 그렇다고 말할 수 밖에 없습니다. ^^ )

위의 소스를 컴파일을 해 만들어진 .class파일을 editor를 이용해서 열어 보면 Hello가 하나만 있는것을 알 수 있죠.

사용자 삽입 이미지


그렇다면 아래의 코드를 한번 보도록 합시다.

  String str1 = new String("Hello");
  String str2 = new String("Hello");
  String str3 = new String("Hello");

  System.err.println(System.identityHashCode(str1));
  System.err.println(System.identityHashCode(str2));
  System.err.println(System.identityHashCode(str3));

결과
3526198
7699183
14285251

위의 소스는 str1, str2, str3는 모두 new String(String)을 이용해서 만들어 보았습니다.
그렇게 했더니 str1, str2, str3의 hashcode가 서로 다르게 나왔습니다. 이는 서로 다른 객체임을 뜻합니다.

"Hello"를 가지고 String을 new 했으나,
실제로 만들어진 객체는 "Hello"라고 인자로 준객체가 아니라, Hello를 가지는 새로운 객체를 만들어 준것입니다.


위의 소스를 컴파일해서 실행하면, 총 4개의 "Hello"가 메모리에 로딩되게 됩니다.

1. 위의 java화일에서 "Hello"라고 되어 있는 놈 ( String pool영역에 있음 )
2. "Hello"를 이용해서 새로운 String을 만들어준 str1이 가리키고 있는 놈 ( Heap에 있음 )
3. "Hello"를 이용해서 새로운 String을 만들어준 str2이 가리키고 있는 놈 ( Heap에 있음 )
4. "Hello"를 이용해서 새로운 String을 만들어준 str3이 가리키고 있는 놈 ( Heap에 있음 )


헉!! 이런 무려 4개나 만들어진단 말야?
왜 똑같은 String인데 4개나 만들어야 하는거야? 그냥 무조건 1개만 쓰도록 하면 되지 않을까?

그래서 String에는 intern()이라는 놈이 있습니다.
intern() 메소드는, 새롭게 만들어진 String객체를 상수화 시켜 줍니다.
만들어진 String 객체가 이미 상수로 만들어진 문자열이라면, 지금 만들어진 놈을 버리고, 상수를 가리키게 합니다.
즉, Heap에 새롭게 만들어진 객체를 버리고, 상수를 재활용하도록 하게 하는것이죠.

뭔말인지 모르겠다면, 예제를 보면 알 수 있습니다.
intern()을 사용한 아래의 예제를 보겠습니다.

  String str1 = "Hello";
  String str2 = new String("Hello").intern();
  String str3 = new String("Hello").intern();
  System.err.println(System.identityHashCode(str1));
  System.err.println(System.identityHashCode(str2));
  System.err.println(System.identityHashCode(str3));

결과
3526198
3526198
3526198

우와! 세상에! 결과로 모두 같은 값이 나왔습니다.
str1에 대한 "Hello"는 상수에 있는 놈을 가리키고 있습니다.
str2는 새로운 String 객체를 만들었지만, intern()을 호출하여 만들어진 객체를 버리고 "Hello" 상수를 가리키게 했습니다.
str3도 str2와 같은 작업을 하게 됩니다.

예전코드는 무려 4개나 Hello 객체가 있었지만, 지금은 딸랑 1개만 있습니다.
우와! 세상에 이렇게 좋은게 있다니!!
오예~ 그럼 이제 무조건 intern()을 써야 겠다!!
라고 생각하면 큰일입니다.

intern에는 엄청난 함정이 있습니다.
intern에 대한 API문서를 확인해 보도록 하죠.


intern

public String intern()
Returns a canonical representation for the string object.

A pool of strings, initially empty, is maintained privately by the class String.

When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.

All literal strings and string-valued constant expressions are interned. String literals are defined in §3.10.5 of the Java Language Specification

Returns:
a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings.

너무 기니깐, 굵게 표시해둔 글자만 대충(!) 읽어 보도록 하면,
intern 메소드가 호출이 되면,

1. String pool에 있는 각종 문자열에 equals해서 같은게 있다면 그 놈을 반환하고,
2. 같은게 없다면 String pool에 String object를 추가하고, 추가한 놈을 반환한다.

intern은 Heap에 만들어진 객체를 놓아주고, String pool에 있는 객체를 가리키게 합니다.
그렇게 됨으로써 Heap의 메모리를 아낄 수 있습니다.

하지만 intern() 메소드를 사용함으로 인해서 손해를 보는것도 생각해 보아야 합니다.
1. 우선 String 객체를 하나 만들어야 합니다.
2. String의 equals 메소드를 이용해서 String pool에 있는 놈을 찾아서 비교해야 합니다. ( 시간이 걸림 )
3. String pool에 들어 갔으므로, 더 이상 GC(가비지컬렉션)의 대상이 될 수 없습니다. ( 메모리 관리 불가 )

시간이 얼마나 걸리는지 간단한 예제를 가지고 테스트 해보도록 하면 아래의 결과를 얻을 수 있습니다.

  long startTime = System.currentTimeMillis();
  for ( int i = 0 ; i < 5000000; i++ ) {
       String str = "Hello";
  }
  long endTime = System.currentTimeMillis();
  System.err.println("String pool = "  + ( endTime - startTime));
 
  long startTime1 = System.currentTimeMillis();
  for ( int i = 0 ; i < 5000000; i++ ) {
       new String("Hello");
  }
  long endTime1 = System.currentTimeMillis();
  System.err.println("new String = "  + ( endTime1 - startTime1));
 
  long startTime2 = System.currentTimeMillis();
  for ( int i = 0 ; i < 5000000; i++ ) {
       new String("Hello").intern();
  }
  long endTime2 = System.currentTimeMillis();
  System.err.println("new String intern = "  + ( endTime2 - startTime2));


결과
String pool = 31
new String = 188
new String intern = 1796


intern을 하게 되면, new String하는 시간과 String pool을 뒤지면서 equals하는 시간까지 걸리므로
당연히 그 속도가 느릴 수 밖에 없습니다.

그리고
보통 intern()의 특징인 "메모리를 아낄 수 있다" 만 생각을 하고 프로그래밍을 하지,
intern()하는데 시간이 오래 걸린다와, GC의 대상이 될 수 없다. 라는것은 생각하지 않고 프로그래밍을 하지요.

특히, "메모리를 아낄 수 있다" 라는 생각에 10번도 안쓰는 String 객체를 intern()을 이용해서 상수화 시키게 되면,
영원히 GC의 대상이 되지 않기 때문에 오히려 "메모리를 버리는 꼴"이 되는것을 조심해야 합니다.

똑같은 문자열을 가지는 String을 100개를 생성한다고 해도, 나중에 GC가 되어서
메모리에서 사라질 가능성이 있다면, 오히려 그것이 메모리를 아낄 수 있는 길입니다.


결론.
intern을 제대로 이해하지 못하고 사용한다면,
메모리를 아끼는것이 아니라, 메모리를 마음껏 버리는 짓을 하게 될 것입니다.


- 내용추가(2013.10.23)
생각해 보니 String.intern()된 애들도 GC의 대상에 포함될 수 있을 듯 합니다.
CustomClassLoader를 만든뒤에 Class를 loading했을때, CustomClassLoader가 GC가 되면class자체에 대한 reference도 없어 질테니, class에 설정된 String들도 GC의 대상이 될 가능성이 있겠네요.



-----
잘못된 내용이나, 오해할 소지가 있는 내용은 언제든지 코멘트 남겨 주세요~ ^^
( 상수와 String pool은.. 참.. 애매하군요. 설명하기에.. )

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
  1. 옷장수 2008.12.15 15:38 신고

    오~ 썬메일에 찬찬찬의 블로그가 떴어요~

    • Chan 2008.12.16 10:24 신고

      오우. 월요일 휴가라서 오늘에서야 보네요~ ㅎ
      방문 감사~ ^^

  2. 버리 2008.12.15 18:57 신고

    ㅋㅋ 저도 썬메일보고 반가운 마음에 들렀어요.
    내가 아는 사람 블로그다! 하구요.ㅎㅎ

    • Chan 2008.12.16 10:25 신고

      신기하군요 -_- 제 글이 뜨다니.. 풉;

  3. UncleJoe 2008.12.16 14:10 신고

    그냥 지나치고 간과할수 있는 부분에 대해서 다시 생각할수 있는 좋은 글이네요.

  4. cetauri 2009.12.16 15:32 신고

    구글링중 찬대리님 블로그가 제일 위에 나오네요 ㅋㅋ

    • Chan 2009.12.19 16:55 신고

      이런데서 ;; 신분을 밝혀 버리다니~~

  5. 2010.08.30 15:58

    비밀댓글입니다

    • 2010.08.30 19:18 신고

      저도 정확하게는 잘 모르지만

      1. permanent영역도 GC가 되나요? 제가 알기론 클래스와 메소드가 들어 있는것으로 아는데, 그게 GC가 된다 치고, 그렇다면 있던 class가 GC가 되고나면, 똑같은 class인데 class loading이 두번 일어 날까요? ^^ 전 그렇지는 않을꺼라 생각합니다. ^^

      2. 어쩔때 사용하면 좋은지는 저도 잘 모르겠습니다. ^^
      String.intern()에 관련된 자료를 몇개 찾아보면, "String.equals()를 쓰지 않고, == 로 비교할 수 있기 때문에 비교연산이 많은 경우에 유리하다" 라고 되어 있지만, 그렇게 사용하려면 관련된 모든 String들을 intern() 시켜야 하겠지요. 코드가 매우 잘 만들어져 있지 않다면, intern()시켜야 할 지점을 잡아 내는게 쉽지 않으리라 생각합니다.

      그리고 String.intern()을 호출하게 되면 어차피 이미 메모리에 올라가 있는 String들을 대상으로 String.equals과 비슷한 일을 해야 할 것입니다. 그렇게 치면 "반드시 ==를 이용하기 때문에 비교 연산이 빠르다" 라고 할 수는 없을 듯 합니다. ^^ ( 하지만 다른분의 글에서는 확실히 빠르다고는 합니다. ㅎㅎ )

      예상할 수 있는 String만 온다면 문제가 없겠지만, 사용자의 입력에 의해서 좌우되는 String이라면 절대 사용해서는 안되겠죠?? ㅎㅎ.

      저는 일을 하면서 아직도, intern()을 사용해야 할 만한곳을 아직 찾지는 못했습니다. 하지만 intern()의 특성을 잘 이해 할 수 있다면 언젠가는 한 번 써 볼 수 있는 날이 오지 않을까? 생각해 봅니다. ^^

  6. ... 2010.08.31 10:42 신고

    답변달아주셔서..
    덧글달려다 차단됐다고 나오네요.
    글을 남기지는 못하고 갑니다ㅠ

    • 2010.08.31 12:52 신고

      헉.. 왜 차단 되었을까요 T_T
      제가 그렇게 만든건 아닙니다. ㅎㅎ ^^;

  7. 2010.09.02 10:49

    비밀댓글입니다

    • 2010.09.23 02:05 신고

      다시 방문 감사드립니다. ^^
      (뒤늦게 인사드리네요. ㅎ )

  8. Java 2012.06.29 18:06 신고

    - intern은 String pool 에 들어가지만 String pool에 등록되었다고 gc의 대상에서 벗어나는것은
    아닙니다. 예전부터 자주보던 글이라 다른사람들도 오해할것같아서 적게되었습니다.

  9. Java 2012.06.29 18:06 신고

    - intern은 String pool 에 들어가지만 String pool에 등록되었다고 gc의 대상에서 벗어나는것은
    아닙니다. 예전부터 자주보던 글이라 다른사람들도 오해할것같아서 적게되었습니다.

    • 2012.07.08 01:21 신고

      아.. 잘못된 정보었네요. ㅜㅡㅜ
      근데 어떤 경우에 GC의 대상이 되는지 좀 알려주세요. ㅜㅡㅜ

  10. 이정섭 2013.12.20 15:39 신고

    메소드 자체를 synchronized할 필요가 없고
    String으로 key에 따라 synchronized를 해야할 경우 사용하면 좋을거같습니다.
    (key값은 jsp에서)
    synchronized(key){
    .....
    }

    • 2013.12.22 04:06 신고

      아마도 [Java/Tip] Hashtable을 제대로 활용하지 못하는 경우... ( http://blog.ggaman.com/917 ) 이 글의 코멘트에 있는 내용에 답 글을 다신것 같습니다.

      안타깝게도 제 생각은 다릅니다.
      key만으로 synchronized 걸어 주면 안됩니다.
      key는 String 객체이고, String 객체는 매번 새롭게 생성 될 수 있습니다.
      그러므로 똑같은 "A"라도 서로 다른 객체를 여러개 만들 수 있고,
      그러므로 서로 다른 객체를 synchronized 잡는것은 의미가 없을 수 있습니다.
      만약 할 것이라면
      syncfhorized(key.intern()) 으로 처리한다면 할 수 있겠지요.

한,두달 전 부터 1G 노트북 메모리를 구매 하기 위해서
살펴 보다가. 드디어 신청해서, 어제 새벽에 달아 보았다.

메모리가 -_- 잘 안 들어가서 -_- 엄청 고생했는데 -_-
대각선으로 힘을 꾹 주고 눌러야 한다. -_-
그냥 일반 메모리 꼿는 생각의 힘으로는 -_- 잘 들어 가지 않는다. -_-;

원래 512M 였는대,
이번에 1G를 추가 해서 총 1.5G로 돌리고 있다.

최초 한번 부팅이라서,
까만 지렁이 화면까지는 잘 몰랐으나,
Window 가 뜨고 난 뒤에는
200% 체감 속도로 빠르게 부팅 되는것이 느껴진다.

오우~ 좋아~
이제 좀 덜 버벅 거려보자~ ㅋㅋ

신고
크리에이티브 커먼즈 라이선스
Creative Commons License
  1. 옷장수 2007.06.14 13:28 신고

    빠르게 느끼는게 한... 한달정도 밖에 안갈것 같은데요.

  2. 지민아빠 2007.06.14 15:05 신고

    맞아요! 한달만 지나면 체감 없음

  3. 박서은 2007.06.14 16:11 신고

    한달도 안 갈텐데. 암튼 축하해요.느낄수 있을 때 팍팍 느끼라고.

  4. 쇼니 2007.06.15 15:20 신고

    한달 후에 업그레이드 하렴~~ㅋ

    • Chan 2007.06.15 19:00 신고

      업그레이드 할려면 남는 512M 를 처리해야 해서 ;;

+ Recent posts