참조 자료형 중 String에 대해 자세히 알아보자

nkcho·2023년 2월 2일

개념 아카이빙

목록 보기
5/9
post-thumbnail

1. String이란?

  • 일반 명사적 의미 : (여러 개의 사물을 함께 엮어 놓은) 줄
  • 고유 명사적 의미 : 요소가 문자 인코딩과 관련된 문자를 대표하는 일련의 자료값을 저장하고 있는 자료형
  • JVM 내 string 선언 및 저장 방식에 따르면 String이란 힙 메모리 영역 내 저장된 문자열 값을 참조하는 주소값을 식별하거나 저장하는 데이터 방식

2. 자바에서 스트링은 어떤 속성을 가지고 있나?

  • 자바에서 스트링은 ‘문자열 객체'다. 객체인데 문자를 배열로 만든 객체다.
  • 자바에서 배열은 불변성(immutable : 불변 : 생성 후 그 상태를 바꿀 수 없음.)을 가지고 있으니, 스트링도 불변성을 가지고 있다고 이해할 수 있다.
    • 스트링의 불변성
      • str이라는 변수 내 문자열의 내용이 바뀌면 새로운 객체가 생성되고, 그 객체의 주소값이 변수에 할당된다. 현재 힙에는 “문자열"이라는 객체와 “문자열2”라는 두 개의 객체가 생성되어 있는 상태인 것.
      • 스트링은 최초에 한 번 생성되면 그 값이 변하지 않는다.

3. 왜 자바는 스트링을 불변객체로 사용하는 걸까?

  1. 스트링 객체의 캐싱 기능

    • 캐싱이란 무엇인가?
      • 캐싱(Caching)은 캐시(Cache)라고 하는 좀 더 빠른 메모리 영역으로 데이터를 가져와서 접근하는 방식을 말한다.
        예를 들어 속도가 느린 하드디스크의 데이터를 메모리로 가지고 와서 메모리 상에서 읽기 쓰기를 수행하는 것을 '데이터를 메모리에 캐싱 한다'라고 한다.
      • 캐시란 무엇인가?
        • 캐시는 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다.
        • 주기억장치와 CPU사이에 위치하고, 자주 사용하는 데이터들을 기억한다.
        • 캐시는 캐시의 접근 시간에 비해 원래 데이터를 접근하는 시간이 오래 걸리는 경우나 값을 다시 계산하는 시간을 절약하고 싶은 경우에 사용한다.
    • 캐싱을 왜 하느냐?
      • 메모리를 절약할라고
        • 문자열은 가장 많이 쓰이는 데이터 타입 중 하나. 따라서 기본적으로 가장 많은 메모리를 차지하게 됨. 많이 쓰이는 데이터일 수록 문자열 객체는 재사용 될 가능성이 높기에 자바는 같은 값일 경우 어플리케이션 당 하나의 String 객체 만을 생성해두어 JVM의 힙(heap)을 절약하고자 함.

          for(int i = 0; i < 500; i++) {
              String str = "Hello World";
              System.out.println(str);
          }
        • 만약 스트링이 불변성의 속성을 가지고 있지 않았다면, 위 코드를 실행할 때 힙에 “Hello World” 객체가 500개나 생성될 것이다. 하지만 불변성이라는 속성 때문에 힙 안에는 “Hello World”라는 객체 하나만 생성되게 되고,
          스택 안에는 해당 객체를 참조하는 참조값만 500개 생성 되었다가 처리가 끝나면 사라지게 된다. (빠른 처리와 메모리 효율 두 장점을 모두 챙길 수 있다!)

      • 데이터 처리 속도를 빠르게 할라고
        • String 객체들은 Heap의 String Pool 이라는 특별한 공간이 있어 이곳에 저장되어 있는 객체를 재사용한다. 객체를 새로 생성하는 수고보다 재사용성이 높을 수록 성능이 향상된다.
  2. 보안기능

    • 불변성을 가지기 때문에 중요한 데이터를 객체에 넣을 경우, 강제로 객체에 저장된 문자열 값을 바꿀 수 없다.
  3. 스레드 안정성

    • 런타임 데이터 환경에는 스레드가 여러개 있고, 스레드가 공유하는 영역이 세 가지 있다. (Heap, Method Area, Runtime Constant Pool)
    • 만약 여러 개의 스레드가 같은 문자열을 사용하려고 한다면, 힙 안에 있는 문자열 객체를 계속 참조하면 된다.
    • 여러 개의 스레드가 참조하여도 참조하는 문자열 객체 자체는 바뀌지 않기 때문에 안정성을 띈다. 정도로만 이해하고 있기.
    • 아래처럼 str에 “문자열2”를 할당해도, 메모리 상으로는 String 객체 자체(”문자열”)를 변경한 것은 아니다. 힙 공간 안에서 “문자열" 자체는 그대로 변하지 않고 살아있다. (→ 다만 해당 객체가 unreachable한 대상이 되었을 때는 GC 대상이 된다)
    String str = "문자열";
    str = "문자열2";

4. 자바에서 String은 어떻게 쓰이나?

  • Memory allotment of String

    • 스트링은 어떻게 선언하느냐에 따라 메모리에 할당하는 방식이 다르다. (=힙 자체에 저장하거나 힙 안에 있는 특정 메모리 영역에 저장하거나.)

      String a = "aaa";
      String b = "aaa";
      String c = new String("aaa");
      String d = new String("aaa");

      • 위 그림을 바탕으로 보자면, 변수 a와 b의 주솟값이 가리키는 객체는 동일하고, c와 d은 객체의 형태는 동일하나 가리키는 주소값이 다르다.
        String a = "aaa";
        String b = "aaa";
        String c = new String("aaa");
        String d = new String("aaa");
         
         
        System.out.println(a == b); //true
        System.out.println(a == c); //false
        System.out.println(c == d); //false
        System.out.println(a.equals(b)); //true
        System.out.println(a.equals(c)); //true 주소값이 아니라 값 자체를 비교하는 거라면 .equals()메소드 사용
        System.out.println(c.equals(d)); //true
    • 리터럴 방식

      • 리터럴 방식으로 선언하게 되면, 문자열 객체는 ‘String Constant Pool’에 저장된다.

        String str = "Geeks";
      • String Constant Pool이란?

        • 힙 내부에 스트링을 보관하기 위한 별도의 메모리 공간을 말함.
        • 힙 안에 있지만 자바 버전에 따라 형태가 조금 다름
          • Java 6 이전
            • Java 6 이전에서의 Spring Pool은 Heap 메모리가 아닌 바로 PermGen(Permenent Generation)에 있었다. 이 PermGen은 메타 클래스 데이터등을 저장해두는 Method 영역에 있는 메모리. 하지만 이 PermGen은 사이즈가 고정(32MB ~ 96MB) 되어있어 너무 많은 String 데이터를 사용한다면 OOM(Out of Memory)이 발생하곤 했음.
          • Java 7
            • Java 7 버전으로 올라오면서 String Pool은 PermGen이 아닌 Heap 메모리 영역으로 위치가 옮겨짐. 따라서 java 7 부터는 더이상 고정된 String Pool 메모리 사이즈 때문에 Out of Memory를 신경쓰지 않아도 됨. 힙 사이즈를 조절하면 String Pool도 함께 영향을 받게 된 것.
          • Java 8
            • Java 8이 되면서 고정 크기로 많은 문제를 일으켰던 PermGen은 Metaspace로 이름을 변경하고, 고정크기에서 동적크기로 메모리 구성이 변경되었음
    • new 연산자(operator) 방식

      • 스트링은 new 연산자를 활용하여 동적으로 메모리(힙)에 할당할 수 있다.

      • 이 경우에는 동적으로 메모리에 할당되기 때문에 힙의 새로운 영역에 저장된다. 즉, String Constant Pool에는 저장되지 않는다.

        String str = new String("Geeks");
      • 만약 new 연산자로 선언된 스트링을 String Constant Pool에 저장하고 싶다면?
        intern() 메소드를 사용하면 String pool에서 같은 값을 갖는 메모리를 저장할 수 있다.

        String s1 = new String("Cat");
        String s2 = s1.intern(); // this will add the string to string constant pool.
        String s3 = "Cat";
         
        System.out.println(s1 == s2);  // false
        System.out.println(s2 == s3);  // true
        System.out.println(s1.intern() == s3);  // true
      • Intern이란?

        • String Constants Pool에 저장하고 사용하는 것을 String Interning 이라고 함.
  • 리터럴 선언과 new 연산자 선언 중 어떤 방법이 더 선호되나? (참고링크)

    • 리터럴 선언이 JVM의 메모리 효율 측면에서 낫다. 왜?
      • JVM의 경우 힙에 새로운 메모리 공간을 계속 만들어야 하지만, 리터럴 선언의 경우
        1. 선언 시 JVM에서 String Constant Pool을 먼저 검토해보고
        2. 객체가 존재하면 해당 객체를 참조하고,
        3. 없으면 그때서야 Pool에 새로운 문자열 객체를 생성하고
        4. Pool 안에 저장하기 때문에 훨씬 메모리 효율 측면에서 경제적이다.
        • 위 과정을 ‘플라이웨이트 패턴'이라고도 부른다.
profile
FE developer

0개의 댓글