-
[Java]STEP1 - 5) 참조 데이터 타입, 메모리 영역, null, NullPointerException, String 타입개발 공부/Java 2020. 9. 5. 23:14
본 게시글은 도서 "이것이 자바다" 동영상 강의를 듣고 기록하는 TIL(Today I Learned) 입니다.
모든 저작권은 출판사 한빛미디어와 신용권님께 있음을 알립니다.👍자바의 데이터 타입은 크게 기본 타입(Primitive Type)과 참조 타입(Reference Type)으로 나눌 수 있다.
기본 타입에 대해서는 이전 게시물에 설명이 되어있다.
이번 글에서는 자바의 또다른 데이터 타입인 참조 타입에 대해서 알아본다.
1. 참조 데이터 타입(Reference type)
기본 타입은 정수, 실수, 문자, 논리 리터럴을 저장하는 타입을 말한다.
참조 타입은 객체(Object)의 번지를 참조하는 타입으로 배열, 열거, 클래스, 인터페이스 타입을 말한다.
기본 타입의 변수가 직접 해당 값을 가지는 것에 반해,
배열, 열거, 클래스, 인터페이스에 의해서 선언된 참조 타입의 변수는 메모리의 번지 수를 값으로 갖는다.
따라서 참조 타입은 직접 변수에 값을 가지는 것이 아니라, 해당 객체의 메모리 번지를 값으로 가진다는 뜻이다.
<기본 타입 변수>
int height = 180;
height라는 변수에 실제로 180이라는 정수가 저장된다.
<참조 타입 변수>
String name = "Kyle Kim";
name이라는 변수에는 힙(Heap)영역에 있는 "Kyle Kim" 이라는 값을 가진 String 객체의 메모리 번지 수가 저장된다.
즉, String 객체가 "Kyle Kim" 이라는 값을 가지고 있고, name은 그 메모리 번지 수만 참조하여 값을 가져오는 것이다.기본 타입 변수의 ==, != 연산은 변수의 값이 같은지, 다른지를 조사한다.
하지만, 참조 타입 변수에서 ==, !=는 동일한 주소의 객체를 참조하는지를 조사한다.
<기본 타입 변수>
int height = 180;
int width = 180;
height == width // true
height와 width에는 모두 180이라는 정수 값이 존재하므로, == 비교 했을 때 true가 나온다.
<참조 타입 변수>
String name = "Kyle Kim"
String word = new String("Kyle Kim");
name == word // false
String 설명에서도 다시 나오겠지만,
name에 직접적으로 "Kyle Kim"으로 문자열 리터럴을 준 경우와
word에 new 연산자를 통해서 새로운 "Kyle Kim" String 객체를 생성한 경우는
서로 참조하는 객체의 메모리 번지 수가 다르기 때문에, 같은 문자열 값이라고 하더라도, == 비교 했을 때 false가 나온다.
이렇듯, 참조 타입 변수는 값 그 자체를 비교하는 것이 아니라, 어떤 주소의 객체를 참조하는지
그 객체의 주소 값을 비교하기 때문에, 값이 똑같더라도 비교 연산의 결과가 다를 수 있다.
2. 메모리 사용 영역(Runtime Data Area)
java.exe에 의해 JVM(자바 가상 머신)이 시작되면 JVM은 운영체제에서 할당받은 메모리 영역(Runtime Data Area)를 세부 영역으로 구분해서 사용한다.
1. 메소드 영역(Method Area)
- ~.class 파일의 런타임 상수풀, 필드 데이터, 메소드 데이터, 메소드 코드, 생성자 코드 등을 분류하여 저장한다.
- JVM이 시작할 때 메소드 영역이 생성되고, 모든 스레드가 공유하는 영역이다.
2. 힙 영역(Heap Area)
- 각 스레드 & 프레임에서 사용되는 객체와 배열이 생성되는 영역이다.
- 참조하는 필드나 변수가 없다면 의미없는 객체가 되므로 JVM이 쓰레기 수집기(Garbage Collector)를 실행하여 쓰레기 객체를 자동으로 제거한다.
- 따라서 개발자는 메모리 영역에서 객체를 제거하기 위한 별도의 코드 작성이 필요 없다.
3. JVM 스택 영역(Stack Area)
- 각 스레드 마다 하나씩 존재하며, 스레드가 시작 될 때 할당된다.
- 메소드 호출 시 프레임(Frame)을 추가(push)하고, 메소드 종료 시 해당 프레임을 제거(pop)한다.
- 각 프레임 내부에는 변수 스택이 있는데, 변수가 생성(push)되거나 제거(pop)된다.
따라서 정리해보자면 다음과 같다.
1) JVM이 시작되면 메소드 영역에서는 ~.class파일의 바이트 코드 내용을 분석 후 저장한다.
2) 스레드 개수 만큼 JVM 스택 영역이 할당된다.
3) main() 메소드가 호출 되며 main 메소드 프레임이 추가(push) 된다.
4) 물론 main()이외에 다른 메소드도 있다면 해당 메소드 프레임도 추가(push)된다.
5) 각 메소드에서 변수가 생성될 때, 해당 메소드 프레임 내에 로컬 변수 스택이 추가(push)된다.
6) 객체나 배열이 있다면 힙 영역에 해당 객체나 배열이 생성되고, JVM 스택의 변수나 다른 객체 필드에서 참조한다.
7) 실행 블록이 종료되고 변수가 필요 없어지면, 해당 메소드 프레임 내의 로컬 변수가 제거(pop)된다.
8) 변수가 제거 되면서 더이상 힙 영역의 객체나 배열을 참조하지 않으면 해당 객체와 배열도 쓰레기 수집기에 의해서 제거된다.
9) 메소드가 끝나면 해당 메소드 프레임도 제거(pop)된다.
10) 이렇게 차례차례 실행되다가 결국 main() 메소드도 종료되면 해당 스레드도 제거된다.
11) 프로그램이 종료되며 JVM도 종료되고 할당받은 메모리 영역(Runtime Data Area)도 사라진다.
3. null, NullPointerException
참조 타입 변수는 힙 영역의 객체를 참조하지 않는다는 뜻으로 null(널) 값을 가질 수 있다.
오직 null은 참조 타입 변수만 가질 수 있고, null 값으로 초기화 할 수 있다.
String name = null;
이때 문자열 name은 null값을 가지게 된다.
즉 name이 힙 영역에서 참조하는 어떠한 String객체도 없다는 뜻이다.자바에서는 실행 도중 발생하는 오류를 예외(Exception)이라고 하는데, null과 관련된 예외도 존재한다.
NullPointerException이라는 예외는 참조 변수가 null 값을 가지는데, 해당 참조 변수를 사용하려고 할 때 발생한다.
int[] number = null;
number[1] = 200;
이때 NullPointerException이 발생한다.
number 배열은 현재 null 값으로 초기화가 되어있기 때문에, 어떠한 힙 영역의 배열 객체도 참조하지 않는다.
그런데 number의 index 1 위치에 200이라는 값을 넣으려고 하기 때문에 이러한 에러가 발생하는 것이다.
따라서 NullPointerException 에러가 발생하게 되면, 현재 참조 타입 변수가 힙 영역의 객체를 참조 하지 않는데
해당 참조 타입 변수를 사용하고 있진 않는지 점검할 필요가 있다.
4. String 타입
자바는 문자열을 저장하기 위한 String 객체를 제공한다.
String name;
name = "Kyle Kim";
위 처럼 변수를 먼저 선언하고, 문자열 리터럴을 대입해도 되고
String name = "Kyle Kim";
처럼 변수의 선언과 리터럴 대입을 동시에 해도 된다.String은 참조 타입 변수이기 때문에 직접 문자열 값을 가지는 것이 아니라,
문자열 값은 힙 영역에 String 객체로 생성되고, 변수는 String 객체를 참조하는 것이다.
위에서도 언급했었지만, 문자열 리터럴이 동일하다면 같은 String 객체를 공유하도록 되어 있다.
반대로 같은 문자열 값이라도, new 연산자를 통해 String을 선언하였다면 다른 String 객체를 사용한다.
String name = "Kyle Kim";
String word = "Kyle Kim";
name과 word는 같은 String 객체를 참조하도록 되어있다.
그래서 name == word는 true가 나온다.
String name = "Kyle Kim";
String word = new String("Kyle Kim");
name과 word 모두 같은 "Kyle Kim"의 값을 가리키지만, 참조하는 String 객체의 주소 값이 다르다.
new 연산자를 사용하면 힙 영역에 새로운 String 객체를 만들기 때문에 다른 객체를 참조하는 것이다.
그래서 name == word는 false가 나온다.
문자열 리터럴과 new 연산자에 의해서 생성된 String 변수의 값을 비교하려면
==, != 연산자가 아니라, equals() 메소드를 이용해야한다.
name.equals(word) 는 name과 word가 참조하는 객체의 주소를 비교하는 것이 아니라
name과 word가 가리키는 값인 "Kyle Kim"을 비교하는 것이므로 true가 나온다.참조를 잃은 String은 JVM에서 쓰레기 수집기(Garbage Collector)를 통해서 힙 영역에서 String 객체를 자동 삭제한다.
String name = "Kyle Kim";
name = null;
이럴 경우, 힙 영역에서 "Kyle Kim"이 담겨있던 String 객체는 참조를 잃어버렸으므로 쓰레기가 된다.
따라서 해당 String 객체는 힙 영역에서 쓰레기 수집기에 의해 삭제된다.본 게시글은 여기서 마치겠습니다.
읽어주셔서 감사하고, 혹시나 틀린 부분이나 보완해야할 부분이 있다면 댓글에 남겨주세요~!
'개발 공부 > Java' 카테고리의 다른 글