[Java 1.8] Data Type의 분류와 메모리 사용 영역

우당탕탕·2024년 7월 28일

Java

목록 보기
1/3

1. Data Type의 분류

데이터 타입은 크게 Primitive type(기본(원시) 타입)과 Reference type(참조 타입)으로 분류된다. 이를 그림으로 나타내면 다음과 같이 나눠진다.

Primitive type으로 선언된 변수와 Reference type으로 선언된 변수의 가장 큰 차이점 !!

  • Primitive type: 실수, 정수 등 한 가지의 저장되는 실제 값
  • Reference type: 객체를 가리키는 참조 값 (이 참조 값은 JVM에 실제 저장된 메모리 주소와 1대1 매핑 되어있음)

2. 메모리 사용 영역

JVM은 운영체제로부터 Runtime Data Area를 다음과 같이 세분화하여 사용한다.


지금부터 각각의 데이터 영역을 세분화해서 살펴보자

메소드(Method) 영역

메소드 영역에서는 코드에서 사용되는 클래스들을 클래스 로더로 읽어 클래스별로 런타임 상수풀(Runtime constant pool), 클래스에 대한 정보(Field, Method 데이터), 메소드 코드, 생성자 코드 등을 분류하여 저장한다. 상수풀은 클래스 파일 포맷에서 constant_pool 테이블에 해당하는 영역으로 상수 뿐만 아니라 메서드와 필드에 대한 모든 레퍼런스(물리적인 메모리 주소)를 저장하고 있으며, 런타임 시 JVM은 상수 풀에 저장된 실제 메모리 주소를 찾아 참조합니다. 이때 클래스에 대한 정보인 필드 데이터와 메서드 데이터의 경우 해당 하는 이 아니라 코드 자체(정의부) 가 저장되는 영역이다. 메소드 영역은 JVM이 시작될 때 생성되고 모든 쓰레드가 이를 공유하게 된다.

힙(Heap) 영역

힙 영역은 모든 인스턴스(객체, 배열 ...)이 생성되는 공간이며 메소드 영역과 같이 모든 쓰레드가 이를 공유하게 된다. 힙 영역에 생성된 인스턴스 들은 JVM 스택 영역의 변수나 다른 객체의 필드에서 참조한다. 참조하는 변수나 필드가 없어지게 되면 자바의 Garbage Collector가 힙 영역에서 자동으로 제거해준다.

JVM 스택(Stack) 영역

JVM 스택은 메소드의 지역변수, 매개변수, 임시변수 등이 저장되며 각 쓰레드 별로 하나씩 생성되며 쓰레드가 시작될 때 할당된다. 프로그램 실행 시 메소드가 호출되면 스택에 push되게 되며, 메소드가 종료되면 차례대로 스택에서 pop된다.
이때 프레임 내부에는 로컬 변수 스택이 있는데, 기본 타입 변수와 참조 타입 변수가 선언될 때 push되고, 사용이 종료될 시점(선언된 블록을 벗어날 때)에 pop되게 된다.

3. 유의사항

3.1 String type

String 타입은 대표적인 Reference type 중 하나이다.

  1. 문자 리터럴의 경우 다음과 같은 성질을 가지게 된다.
String fruit = "Apple";
String fruit2 = "Apple"; 
fruit == fruit2; // true
  • String타입의 fruit 변수를 선언하면 Stack에 생성되고 문자 리터럴 "Apple"은 메소드 영역에 생성되어 fruit는 Apple의 참조 값을 가지게 된다.
  • 이때 fruit2는 위에서 메소드 영역(상수풀)에 생성된 문자 리터럴 "Apple"의 참조 값을 그대로 가지게 된다.
  • 그래서 fruit와 fruit2의 논리 비교 연산 결과는 true다.

  1. 인스턴스를 생성하여 사용할 경우 다음과 같은 성질을 가지게 된다.
String fruit = new String("Apple"); 
String fruit2 = new String("Apple");
fruit == fruit2; // false
  • String 타입의 fruit 변수를 선언하면, Stack에 생성되고 new 연산을 하는 경우 String 인스턴스가 힙에 생성되게 된다. 그리고 fruit는 해당 인스턴스의 참조 값을 가지게 된다.
  • 이 역시, fruit2는 새롭게 생성된 두번째 인스턴스의 참조 값을 가지게 된다.
  • 그래서 fruit와 fruit2의 논리 비교 연산 결과는 false다.

  1. 그래서 fruit과 fruit2의 문자열을 비교하기 위해서는 논리 비교 연산자를 사용하면 안된다.
    왜냐하면 둘 다 문자 리터럴 값을 그대로 가지고 있는 게 아니라 참조 값을 가지고 있는 것이기 때문이다. 문자열을 비교하기 위해서는 다음과 같이 equals 메서드를 사용한다. 원본 문자열.equals(비교 문자열 )로 사용하면 된다.

equals는 String에 있는 인스턴스 메서드이다. 안에 담겨있는 문자열 비교 시 사용한다.

fruit.equals(fruit2); //true

3.2 array type

  1. 배열의 경우 다음과 같은 성질을 가지게 된다.
int[] scores = new int[] {10, 20, 30};
int[] scores2 = {10, 20, 30};
int[] scores3 = {10, 20, 30};

scores == scores2; //false
scores2 == scores3; //false
  • int array 타입의 scores 변수를 선언하면, Stack에 생성되고 {}는 주어진 값들을 항목으로 가지는 배열 인스턴스를 힙에 생성하고 참조 값을 생성한다. 그래서 scores는 배열 인스턴스의 참조 값을 가지게 된다. scores2와 scores3의 경우도 이와 동일하게 각각의 배열 인스턴스의 참조 값을 가지고 있다. 배열을 생성할 때 new 연산자를 사용하는 이유는 배열의 크기만 알고 지정할 때 먼저 배열의 크기를 지정해주기 위해서 사용한다.
profile
종종 생각나는 거 적는 우당탕 개발일지...

0개의 댓글